ytptube/ui/app/composables/useDocs.ts
2026-03-26 19:12:40 +03:00

171 lines
4 KiB
TypeScript

export type DocsFile = 'README.md' | 'FAQ.md' | 'API.md';
export type DocsEntry = {
id: string;
title: string;
description: string;
file: DocsFile;
route: string;
slug: string[];
icon: string;
navLabel: string;
};
const DOCS_ASSETS = ['sc_short.jpg', 'sc_simple.jpg'] as const;
const DOCS_ENTRIES: DocsEntry[] = [
{
id: 'readme',
title: 'README',
description: 'Project overview',
file: 'README.md',
route: '/docs/readme',
slug: ['readme'],
icon: 'i-lucide-book-open',
navLabel: 'README',
},
{
id: 'faq',
title: 'FAQ',
description: 'Frequently asked questions about the project and troubleshooting.',
file: 'FAQ.md',
route: '/docs/faq',
slug: ['faq'],
icon: 'i-lucide-circle-help',
navLabel: 'FAQ',
},
{
id: 'docs-api',
title: 'API',
description: 'HTTP API reference and endpoint behavior.',
file: 'API.md',
route: '/docs/api',
slug: ['api'],
icon: 'i-lucide-code-xml',
navLabel: 'API',
},
];
const DOCS_ROUTE_BY_FILE = new Map(DOCS_ENTRIES.map((entry) => [entry.file, entry.route]));
const normalizeSlugParts = (slug?: string | string[]): string[] => {
if (!slug) {
return [];
}
return (Array.isArray(slug) ? slug : [slug])
.map((part) => part.trim().toLowerCase())
.filter(Boolean);
};
const getDocsEntryBySlug = (slug?: string | string[]): DocsEntry | undefined => {
const parts = normalizeSlugParts(slug);
if (parts.length === 0) {
return DOCS_ENTRIES[0];
}
const key = parts.join('/');
if (key === 'readme') {
return DOCS_ENTRIES[0];
}
return DOCS_ENTRIES.find((entry) => entry.slug.join('/') === key);
};
const getDocsNavigationEntries = () =>
DOCS_ENTRIES.map((entry) => ({
id: entry.id,
label: entry.navLabel,
description: entry.description,
icon: entry.icon,
to: entry.route,
}));
const extractKnownDocsTarget = (href: string): { name: string; hash: string } | undefined => {
if (!href || href.startsWith('#')) {
return undefined;
}
try {
const parsed = new URL(href, window?.origin || 'http://localhost');
const segments = parsed.pathname.split('/').filter(Boolean);
const name = segments.at(-1) || '';
if (
DOCS_ROUTE_BY_FILE.has(name as DocsFile) ||
DOCS_ASSETS.includes(name as (typeof DOCS_ASSETS)[number])
) {
return { name, hash: parsed.hash || '' };
}
} catch {}
return undefined;
};
const resolveDocsLink = (href: string): { href: string; external: boolean; docRoute?: string } => {
if (!href) {
return { href, external: false };
}
if (href.startsWith('#')) {
const route = window?.location?.pathname || '';
const currentRoute = `${route}${href}`;
return { href: currentRoute, external: false, docRoute: currentRoute };
}
try {
const parsed = new URL(href, window?.origin || 'http://localhost');
const currentPath = window?.location?.pathname || '';
if (
parsed.origin === (window?.origin || parsed.origin) &&
parsed.hash &&
parsed.pathname === currentPath
) {
const currentRoute = `${parsed.pathname}${parsed.hash}`;
return { href: currentRoute, external: false, docRoute: currentRoute };
}
} catch {}
const match = extractKnownDocsTarget(href);
if (!match) {
return { href, external: true };
}
const docsRoute = DOCS_ROUTE_BY_FILE.get(match.name as DocsFile);
if (docsRoute) {
const route = `${docsRoute}${match.hash}`;
return {
href: route,
external: false,
docRoute: route,
};
}
return {
href: `/api/docs/${match.name}`,
external: false,
};
};
const resolveDocsImageSrc = (href: string): string => {
const match = extractKnownDocsTarget(href);
if (!match) {
return href;
}
if (DOCS_ASSETS.includes(match.name as (typeof DOCS_ASSETS)[number])) {
return `/api/docs/${match.name}`;
}
return href;
};
export {
DOCS_ENTRIES,
getDocsEntryBySlug,
getDocsNavigationEntries,
resolveDocsImageSrc,
resolveDocsLink,
};