mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): add changelog section (#1793)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { Guide, Section, Sidebar } from '../../../types/docs';
|
||||
import type { Guide, Section, Sidebar, SidebarLink } from '../../../types/docs';
|
||||
import {
|
||||
filterSidebar,
|
||||
findFirstGuide,
|
||||
@@ -419,6 +419,49 @@ describe('sidebar utilities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('SidebarLink handling', () => {
|
||||
const mockLink: SidebarLink = {
|
||||
href: '/changelog',
|
||||
sidebarLabel: 'Changelog',
|
||||
};
|
||||
|
||||
it('should pass links through filterSidebar', () => {
|
||||
const sidebar: Sidebar = [{ sidebarLabel: 'Section', contents: [mockGuide1, mockLink] }];
|
||||
const result = filterSidebar('html', sidebar);
|
||||
|
||||
expect((result[0] as Section).contents).toContainEqual(mockLink);
|
||||
});
|
||||
|
||||
it('should filter out devOnly links in production', () => {
|
||||
const devLink: SidebarLink = { ...mockLink, devOnly: true };
|
||||
const sidebar: Sidebar = [{ sidebarLabel: 'Section', contents: [mockGuide1, devLink] }];
|
||||
const result = filterSidebar('html', sidebar, false);
|
||||
|
||||
expect((result[0] as Section).contents).toEqual([mockGuide1]);
|
||||
});
|
||||
|
||||
it('should skip links in findFirstGuide', () => {
|
||||
const sidebar: Sidebar = [mockLink, mockGuide3];
|
||||
const result = findFirstGuide('html', sidebar);
|
||||
|
||||
expect(result).toBe('guide-3');
|
||||
});
|
||||
|
||||
it('should exclude links from getAllGuideSlugs', () => {
|
||||
const sidebar: Sidebar = [{ sidebarLabel: 'Section', contents: [mockGuide1, mockLink] }, mockGuide3];
|
||||
const result = getAllGuideSlugs(sidebar);
|
||||
|
||||
expect(result).toEqual(['guide-1', 'guide-3']);
|
||||
});
|
||||
|
||||
it('should ignore links in findGuideBySlug', () => {
|
||||
const sidebar: Sidebar = [mockLink, mockGuide3];
|
||||
const result = findGuideBySlug('guide-3', sidebar);
|
||||
|
||||
expect(result).toEqual(mockGuide3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sidebar config validation', () => {
|
||||
it('should not have duplicate slugs in sidebar config', async () => {
|
||||
// Import the real sidebar config
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Guide, Section, Sidebar, SupportedFramework } from '@/types/docs';
|
||||
import { FRAMEWORK_STYLES, isSection } from '@/types/docs';
|
||||
import type { Guide, Sidebar, SidebarItem, SupportedFramework } from '@/types/docs';
|
||||
import { FRAMEWORK_STYLES, isLink, isSection } from '@/types/docs';
|
||||
|
||||
import { sidebar } from '../../docs.config';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { sidebar } from '../../docs.config';
|
||||
* @returns true if the item should be visible
|
||||
*/
|
||||
export function isItemVisible(
|
||||
item: Guide | Section,
|
||||
item: SidebarItem,
|
||||
framework: SupportedFramework,
|
||||
isDev: boolean = import.meta.env.DEV
|
||||
): boolean {
|
||||
@@ -94,7 +94,7 @@ export function findFirstGuide(
|
||||
} catch {
|
||||
// Continue searching other sections
|
||||
}
|
||||
} else {
|
||||
} else if (!isLink(item)) {
|
||||
// It's a Guide, return its slug
|
||||
return item.slug;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export function getAllGuideSlugs(sidebarToExtract: Sidebar = sidebar): string[]
|
||||
if (isSection(item)) {
|
||||
// Recursively get slugs from section contents
|
||||
slugs.push(...getAllGuideSlugs(item.contents));
|
||||
} else {
|
||||
} else if (!isLink(item)) {
|
||||
// It's a Guide, add its slug
|
||||
slugs.push(item.slug);
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export function findGuideBySlug(slug: string, sidebarToSearch: Sidebar = sidebar
|
||||
// Recursively search section contents
|
||||
const guide = findGuideBySlug(slug, item.contents);
|
||||
if (guide) return guide;
|
||||
} else if (item.slug === slug) {
|
||||
} else if (!isLink(item) && item.slug === slug) {
|
||||
// Found the guide
|
||||
return item;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ export function getSectionsForGuide(slug: string, sidebarToSearch: Sidebar = sid
|
||||
// Recursively search section contents with updated path
|
||||
const result = findInSidebar(item.contents, [...path, item.sidebarLabel]);
|
||||
if (result !== null) return result;
|
||||
} else if (item.slug === slug) {
|
||||
} else if (!isLink(item) && item.slug === slug) {
|
||||
// Found the guide, return the accumulated path
|
||||
return path;
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export function getValidFrameworksForGuide(guide: Guide, sidebarToSearch: Sideba
|
||||
const sectionFrameworks = item.frameworks ? inherited.filter((f) => item.frameworks!.includes(f)) : inherited;
|
||||
const result = findWithRestrictions(item.contents, sectionFrameworks);
|
||||
if (result) return result;
|
||||
} else if (item.slug === guide.slug) {
|
||||
} else if (!isLink(item) && item.slug === guide.slug) {
|
||||
return item.frameworks ? inherited.filter((f) => item.frameworks!.includes(f)) : inherited;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const STATIC_PAGES: { path: string; title: string }[] = [
|
||||
{ path: 'blog', title: 'Blog' },
|
||||
];
|
||||
|
||||
export type OgTitleEntryKind = 'static' | 'blog' | 'blog-index' | 'author' | 'docs';
|
||||
export type OgTitleEntryKind = 'static' | 'blog' | 'blog-index' | 'author' | 'docs' | 'changelog' | 'changelog-index';
|
||||
export type OgTitleSource = 'static' | 'title' | 'ogTitle' | 'frameworkTitle' | 'name';
|
||||
|
||||
export interface OgTitleEntry {
|
||||
@@ -60,6 +60,36 @@ export async function listOgTitleEntries(): Promise<OgTitleEntry[]> {
|
||||
});
|
||||
}
|
||||
|
||||
const changelogEntries = await getCollection('changelog');
|
||||
|
||||
entries.push({
|
||||
kind: 'changelog-index',
|
||||
path: 'changelog',
|
||||
title: 'Changelog',
|
||||
source: 'static',
|
||||
});
|
||||
|
||||
for (const entry of changelogEntries) {
|
||||
entries.push({
|
||||
kind: 'changelog',
|
||||
path: `changelog/${entry.id}`,
|
||||
title: `v${entry.data.version} Changelog`,
|
||||
source: 'title',
|
||||
collectionId: entry.id,
|
||||
});
|
||||
}
|
||||
|
||||
const totalChangelogPages = Math.ceil(changelogEntries.length / BLOG_PAGE_SIZE);
|
||||
|
||||
for (let page = 2; page <= totalChangelogPages; page += 1) {
|
||||
entries.push({
|
||||
kind: 'changelog-index',
|
||||
path: `changelog/${page}`,
|
||||
title: 'Changelog',
|
||||
source: 'static',
|
||||
});
|
||||
}
|
||||
|
||||
const authors = await getCollection('authors');
|
||||
|
||||
for (const author of authors) {
|
||||
|
||||
Reference in New Issue
Block a user