mirror of
https://gitea.com/gitea/docs.git
synced 2026-11-04 09:15:33 +00:00
Follow-up to #588. - rename `DocusaurusSidebarItem` -> `SidebarSourceItem`, `fromDocusaurus` -> `fromSidebarSource`, `docusaurusSidebars` -> `sidebarSources` - reword comments and READMEs that described behaviour by comparison with docusaurus; fix the stale note in `sites/docs/src/config/api.ts` - drop `.docusaurus/` from `.gitignore` No functional change. `pnpm check` has 0 errors and `pnpm build` still builds 6690 pages. <!-- cloudflare-preview --> Preview: https://pr-589.docs-gitea-com.pages.dev Reviewed-on: https://gitea.com/gitea/docs/pulls/589 Reviewed-by: silverwind <[email protected]>
171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
import {
|
|
defaultLocale,
|
|
products,
|
|
routePrefix,
|
|
type LocaleId,
|
|
type ProductDef,
|
|
type ProductId,
|
|
type VersionDef,
|
|
} from './products.js';
|
|
|
|
/**
|
|
* One content directory of the matrix: a (product, version, language) triple
|
|
* mapped to its source directory and its route prefix.
|
|
*/
|
|
export interface Segment {
|
|
product: ProductDef;
|
|
version: VersionDef;
|
|
locale: LocaleId;
|
|
/** Source directory, relative to the repository root. */
|
|
dir: string;
|
|
/** Route prefix, without leading or trailing slash. */
|
|
prefix: string;
|
|
}
|
|
|
|
export interface ScopeFilter {
|
|
products?: ProductId[];
|
|
versions?: string[];
|
|
locales?: LocaleId[];
|
|
}
|
|
|
|
/**
|
|
* Reads the build scope from the environment. Used to build a subset of the
|
|
* matrix during development, where loading every version is wasteful.
|
|
*
|
|
* GITEA_DOCS_PRODUCTS=docs,runner GITEA_DOCS_VERSIONS=1.27 pnpm dev
|
|
*/
|
|
export function scopeFromEnv(env: NodeJS.ProcessEnv = process.env): ScopeFilter {
|
|
const list = (value: string | undefined) =>
|
|
value
|
|
?.split(',')
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
return {
|
|
products: list(env.GITEA_DOCS_PRODUCTS) as ProductId[] | undefined,
|
|
versions: list(env.GITEA_DOCS_VERSIONS),
|
|
locales: list(env.GITEA_DOCS_LOCALES) as LocaleId[] | undefined,
|
|
};
|
|
}
|
|
|
|
/** Expands the product matrix into the content directories to load. */
|
|
export function resolveSegments(filter: ScopeFilter = {}): Segment[] {
|
|
const segments: Segment[] = [];
|
|
for (const product of products) {
|
|
if (product.externalBaseUrl) continue;
|
|
if (filter.products && !filter.products.includes(product.id)) continue;
|
|
for (const version of product.versions) {
|
|
if (filter.versions && !filter.versions.includes(version.id)) continue;
|
|
for (const locale of product.locales) {
|
|
if (filter.locales && !filter.locales.includes(locale)) continue;
|
|
const dir = version.sources[locale];
|
|
if (!dir) continue;
|
|
segments.push({
|
|
product,
|
|
version,
|
|
locale,
|
|
dir,
|
|
prefix: routePrefix(product, version, locale),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return segments;
|
|
}
|
|
|
|
/** Finds the segment a repository relative file path belongs to. */
|
|
export function segmentOf(segments: Segment[], file: string): Segment | undefined {
|
|
let match: Segment | undefined;
|
|
for (const segment of segments) {
|
|
if (!file.startsWith(`${segment.dir}/`)) continue;
|
|
// longest directory wins, `docs` must not swallow `docs-foo`
|
|
if (!match || segment.dir.length > match.dir.length) match = segment;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
/**
|
|
* Route id of a source file, following these rules:
|
|
* `index` files address their directory, a relative `slug` replaces the last
|
|
* path segment and `slug: /` addresses the root of the version.
|
|
*/
|
|
export function routeSuffix(segment: Segment, file: string, slug?: string): string[] {
|
|
const relative = file.slice(segment.dir.length + 1).replace(/\.mdx?$/, '');
|
|
const parts = relative.split('/');
|
|
const isIndex = parts.at(-1) === 'index';
|
|
if (isIndex) parts.pop();
|
|
|
|
if (slug === '/') {
|
|
parts.length = 0;
|
|
} else if (slug) {
|
|
if (!isIndex) parts.pop();
|
|
parts.push(...slug.replace(/^\/+|\/+$/g, '').split('/'));
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
/**
|
|
* Route id of a source file. Translations follow the route of their english
|
|
* counterpart, a translated `slug` never moves a page.
|
|
*/
|
|
export function routeIdOf(segment: Segment, suffix: string[]): string {
|
|
// the data store rejects empty ids, starlight maps `index` to the root
|
|
return [segment.prefix, ...suffix].filter(Boolean).join('/') || 'index';
|
|
}
|
|
|
|
/** Path of a source file relative to its segment directory. */
|
|
export function relativeToSegment(segment: Segment, file: string): string {
|
|
return file.slice(segment.dir.length + 1);
|
|
}
|
|
|
|
/** Key identifying the (product, version) a segment translates. */
|
|
export function groupKeyOf(segment: Segment): string {
|
|
return `${segment.product.id}@${segment.version.id}`;
|
|
}
|
|
|
|
/**
|
|
* Works out which (product, version, language) a route belongs to. Used for the
|
|
* pages starlight builds outside of the loader, the api reference in
|
|
* particular, so they get the same pickers, banner and search facets.
|
|
*/
|
|
export function metaFromRouteId(id: string):
|
|
| { product: ProductId; version: string; locale: LocaleId; prefix: string }
|
|
| undefined {
|
|
const route = id === 'index' ? '' : id;
|
|
let best: { product: ProductId; version: string; locale: LocaleId; prefix: string } | undefined;
|
|
for (const product of products) {
|
|
if (product.externalBaseUrl) continue;
|
|
for (const version of product.versions) {
|
|
for (const locale of product.locales) {
|
|
const prefix = routePrefix(product, version, locale);
|
|
// the api pages are generated by starlight-openapi, which slugifies the
|
|
// base path: `api/1.26` is built as `api/126` and renamed afterwards
|
|
const candidates = [prefix, slugifyPrefix(prefix)];
|
|
if (prefix && !candidates.some((candidate) => route === candidate || route.startsWith(`${candidate}/`))) {
|
|
continue;
|
|
}
|
|
if (best && best.prefix.length >= prefix.length) continue;
|
|
best = { product: product.id, version: version.id, locale, prefix };
|
|
}
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/** Same slugification github-slugger applies to a starlight-openapi base path. */
|
|
function slugifyPrefix(prefix: string): string {
|
|
return prefix
|
|
.split('/')
|
|
.map((part) => part.toLowerCase().replace(/[^\w-]/g, ''))
|
|
.join('/');
|
|
}
|
|
|
|
/** Url of a route id, mirroring the starlight slug to pathname rules. */
|
|
export function routeIdToPath(id: string): string {
|
|
if (id === '' || id === 'index') return '/';
|
|
const trimmed = id.endsWith('/index') ? id.slice(0, -'/index'.length) : id;
|
|
return `/${trimmed}/`;
|
|
}
|
|
|
|
export { defaultLocale };
|