mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
237 lines
6.7 KiB
TypeScript
237 lines
6.7 KiB
TypeScript
import { sidebar as defaultSidebar } from '@/docs.config';
|
|
import type { Sidebar, SupportedFramework } from '@/types/docs';
|
|
import { DEFAULT_FRAMEWORK, isValidFramework } from '@/types/docs';
|
|
import { findFirstGuide, findGuideBySlug, getValidFrameworksForGuide } from './sidebar';
|
|
|
|
/**
|
|
* Build a docs URL from framework and guide slug components.
|
|
*/
|
|
export function buildDocsUrl(framework: SupportedFramework, guideSlug: string): string {
|
|
return `/docs/framework/${framework}/${guideSlug}`;
|
|
}
|
|
|
|
/**
|
|
* Input for resolveIndexRedirect
|
|
*/
|
|
export interface IndexRedirectInput {
|
|
preferences: {
|
|
framework: string | null;
|
|
};
|
|
params: {
|
|
framework?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Output from resolveIndexRedirect
|
|
*/
|
|
export interface IndexRedirectResult {
|
|
url: string;
|
|
selectedFramework: SupportedFramework;
|
|
selectedSlug: string;
|
|
reason: string;
|
|
}
|
|
|
|
/**
|
|
* Resolve redirect for index pages (/docs, /docs/framework/X).
|
|
* Nothing is pinned - we must select framework AND slug.
|
|
*
|
|
* Logic:
|
|
* 1. If params.framework → validate → find first guide
|
|
* 2. If no param → get from preferences or defaults → find first guide
|
|
*
|
|
* @param input - The input containing preferences and params
|
|
* @param sidebar - Optional sidebar to search (defaults to main sidebar config)
|
|
*/
|
|
export function resolveIndexRedirect(
|
|
input: IndexRedirectInput,
|
|
sidebar: Sidebar = defaultSidebar
|
|
): IndexRedirectResult {
|
|
const { preferences, params } = input;
|
|
|
|
let selectedFramework: SupportedFramework;
|
|
let reason: string;
|
|
|
|
if (params.framework) {
|
|
// Framework in params - validate it
|
|
if (!isValidFramework(params.framework)) {
|
|
throw new Error(`Invalid framework param: ${params.framework}`);
|
|
}
|
|
selectedFramework = params.framework;
|
|
reason = 'Using validated params.framework';
|
|
} else {
|
|
// No params - use preferences or defaults
|
|
if (preferences.framework && isValidFramework(preferences.framework)) {
|
|
selectedFramework = preferences.framework;
|
|
reason = 'Using preferences.framework';
|
|
} else {
|
|
// Use all defaults
|
|
selectedFramework = DEFAULT_FRAMEWORK;
|
|
reason = 'Using default framework (no valid preferences)';
|
|
}
|
|
}
|
|
|
|
// Find the first guide for the selected framework
|
|
const selectedSlug = findFirstGuide(selectedFramework, sidebar);
|
|
const url = buildDocsUrl(selectedFramework, selectedSlug);
|
|
|
|
return {
|
|
url,
|
|
selectedFramework,
|
|
selectedSlug,
|
|
reason,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Input for resolveFrameworkChange
|
|
*/
|
|
export interface FrameworkChangeInput {
|
|
currentFramework: SupportedFramework;
|
|
currentSlug: string;
|
|
newFramework: SupportedFramework;
|
|
}
|
|
|
|
/**
|
|
* Output from resolveFrameworkChange
|
|
*/
|
|
export interface FrameworkChangeResult {
|
|
url: string;
|
|
shouldReplace: boolean;
|
|
selectedFramework: SupportedFramework;
|
|
selectedSlug: string;
|
|
slugChanged: boolean;
|
|
reason: string;
|
|
}
|
|
|
|
/**
|
|
* Resolve URL when user changes framework selector.
|
|
* newFramework is PINNED (must keep), slug MAY change if not visible.
|
|
*
|
|
* Logic:
|
|
* 1. framework = newFramework (PINNED)
|
|
* 2. If currentSlug visible in newFramework → slug = currentSlug, shouldReplace = true
|
|
* Else → slug = first guide in newFramework, shouldReplace = false
|
|
*
|
|
* @param input - The input containing current state and new framework
|
|
* @param sidebar - Optional sidebar to search (defaults to main sidebar config)
|
|
*/
|
|
export function resolveFrameworkChange(
|
|
input: FrameworkChangeInput,
|
|
sidebar: Sidebar = defaultSidebar
|
|
): FrameworkChangeResult {
|
|
const { currentSlug, newFramework } = input;
|
|
|
|
if (!isValidFramework(newFramework)) {
|
|
throw new Error(`Invalid framework: ${newFramework}`);
|
|
}
|
|
|
|
const selectedFramework = newFramework; // PINNED
|
|
|
|
// Determine the slug to use
|
|
let selectedSlug: string;
|
|
let shouldReplace: boolean;
|
|
let slugChanged: boolean;
|
|
let reason: string;
|
|
|
|
const guide = findGuideBySlug(currentSlug, sidebar);
|
|
const validFrameworks = guide ? getValidFrameworksForGuide(guide, sidebar) : [];
|
|
if (guide && validFrameworks.includes(selectedFramework)) {
|
|
// Current slug is visible in the new framework
|
|
selectedSlug = currentSlug;
|
|
shouldReplace = true;
|
|
slugChanged = false;
|
|
reason = 'Changed framework, kept slug (visible in new framework)';
|
|
} else {
|
|
// Current slug is not visible, find first guide
|
|
selectedSlug = findFirstGuide(selectedFramework, sidebar);
|
|
shouldReplace = false;
|
|
slugChanged = true;
|
|
reason = 'Changed framework, changed slug (slug not visible in new framework)';
|
|
}
|
|
|
|
const url = buildDocsUrl(selectedFramework, selectedSlug);
|
|
|
|
return {
|
|
url,
|
|
shouldReplace,
|
|
selectedFramework,
|
|
selectedSlug,
|
|
slugChanged,
|
|
reason,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Input for resolveDocsLinkUrl
|
|
*/
|
|
export interface DocsLinkInput {
|
|
targetSlug: string;
|
|
contextFramework: SupportedFramework;
|
|
}
|
|
|
|
/**
|
|
* Output from resolveDocsLinkUrl
|
|
*/
|
|
export interface DocsLinkResult {
|
|
url: string;
|
|
selectedFramework: SupportedFramework;
|
|
selectedSlug: string;
|
|
priorityLevel: 1 | 2;
|
|
reason: string;
|
|
}
|
|
|
|
/**
|
|
* Resolve the best URL for a guide slug link given current context.
|
|
* targetSlug is PINNED (must keep), framework MAY change.
|
|
*
|
|
* Logic (2-level priority cascade):
|
|
* 1. slug = targetSlug (PINNED)
|
|
* 2. Try to find best framework that supports targetSlug:
|
|
* - Priority 1: If targetSlug visible in contextFramework → use it (best UX)
|
|
* - Priority 2: Use guide's first valid framework
|
|
*
|
|
* @param input - The input containing target slug and context
|
|
* @param sidebar - Optional sidebar to search (defaults to main sidebar config)
|
|
*/
|
|
export function resolveDocsLinkUrl(input: DocsLinkInput, sidebar: Sidebar = defaultSidebar): DocsLinkResult {
|
|
const { targetSlug, contextFramework } = input;
|
|
|
|
const guide = findGuideBySlug(targetSlug, sidebar);
|
|
if (!guide) {
|
|
throw new Error(`No guide found with slug "${targetSlug}"`);
|
|
}
|
|
|
|
if (!isValidFramework(contextFramework)) {
|
|
throw new Error(`Invalid context framework: ${contextFramework}`);
|
|
}
|
|
|
|
const selectedSlug = targetSlug; // PINNED
|
|
let selectedFramework: SupportedFramework;
|
|
let priorityLevel: 1 | 2;
|
|
let reason: string;
|
|
|
|
// Priority 1: Try current framework
|
|
const validFrameworks = getValidFrameworksForGuide(guide, sidebar);
|
|
if (validFrameworks.includes(contextFramework)) {
|
|
selectedFramework = contextFramework;
|
|
priorityLevel = 1;
|
|
reason = 'Priority 1: Kept framework (slug visible in current context)';
|
|
} else {
|
|
// Priority 2: Fallback to guide's first valid framework
|
|
selectedFramework = validFrameworks[0];
|
|
priorityLevel = 2;
|
|
reason = 'Priority 2: Changed framework (slug not visible in current context)';
|
|
}
|
|
|
|
const url = buildDocsUrl(selectedFramework, selectedSlug);
|
|
|
|
return {
|
|
url,
|
|
selectedFramework,
|
|
selectedSlug,
|
|
priorityLevel,
|
|
reason,
|
|
};
|
|
}
|