diff --git a/.changeset/tender-papayas-walk.md b/.changeset/tender-papayas-walk.md new file mode 100644 index 0000000000..35934609c7 --- /dev/null +++ b/.changeset/tender-papayas-walk.md @@ -0,0 +1,5 @@ +--- +'nextra': patch +--- + +sort `defaultMeta` by `frontMatter.date`, if missing by `frontMatter.title` and after by capitalized page name diff --git a/.changeset/wet-trainers-sleep.md b/.changeset/wet-trainers-sleep.md new file mode 100644 index 0000000000..acd4564941 --- /dev/null +++ b/.changeset/wet-trainers-sleep.md @@ -0,0 +1,5 @@ +--- +'nextra': patch +--- + +capitalize sidebar's folders names if item is missing in `_meta.json` diff --git a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap index 6f2b448cdc..b020554959 100644 --- a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap +++ b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap @@ -550,6 +550,10 @@ exports[`Page Process > pageMap en-US 1`] = ` "data": { "404": "404", "500": "500", + "about": "About", + "blog": "Blog", + "docs": "Docs", + "examples": "Examples", }, "kind": "Meta", }, @@ -564,6 +568,7 @@ exports[`Page Process > pageMap zh-CN 1`] = ` [ { "data": { + "about": "About", "blog": { "title": "博客", "type": "page", @@ -683,6 +688,7 @@ exports[`Page Process > pageMap zh-CN 1`] = ` "data": { "cache": "缓存", "file-name.with.DOTS": "带点的文件名", + "more": "More", "performance": "性能", "react-native": "React Native", }, diff --git a/packages/nextra/__test__/sort-pages.test.ts b/packages/nextra/__test__/sort-pages.test.ts new file mode 100644 index 0000000000..19a9d4714c --- /dev/null +++ b/packages/nextra/__test__/sort-pages.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest' +import { sortPages } from '../src/utils' + +const kind = 'MdxPage' + +describe('sortPages()', () => { + it('should sort by date', () => { + const data = sortPages([ + { kind, name: 'baz', frontMatter: { date: new Date('1995-10-21') } }, + { kind, name: 'foo', frontMatter: { date: new Date('1992-10-21') } }, + { kind, name: 'quz', frontMatter: { date: new Date('1998-10-21') } } + ]) + expect(data).toEqual([ + ['quz', 'Quz'], + ['baz', 'Baz'], + ['foo', 'Foo'] + ]) + }) + + it('should sort by date first and after by title', () => { + const data = sortPages([ + { kind, name: 'quz' }, + { kind, name: 'foo', frontMatter: { date: new Date('1992-10-21') } }, + { kind, name: 'baz' } + ]) + expect(data).toEqual([ + ['foo', 'Foo'], + ['baz', 'Baz'], + ['quz', 'Quz'] + ]) + }) + + it('should take priority `frontMatter.title` over name', () => { + const data = sortPages([ + { kind, name: 'baz' }, + { kind, name: 'foo', frontMatter: { title: 'abc' } }, + { kind, name: 'quz' } + ]) + expect(data).toEqual([ + ['foo', 'abc'], + ['baz', 'Baz'], + ['quz', 'Quz'] + ]) + }) + + it('should sort numeric', () => { + const data = sortPages([ + { kind, name: '10-baz' }, + { kind, name: '0-foo' }, + { kind, name: '2.5-quz' } + ]) + expect(data).toEqual([ + ['0-foo', '0 Foo'], + ['2.5-quz', '2.5 Quz'], + ['10-baz', '10 Baz'] + ]) + }) + + it('should capitalize `Folder`', () => { + const data = sortPages([ + { kind, name: 'quz' }, + { kind: 'Folder', name: 'foo' }, + { kind, name: 'baz' } + ]) + expect(data).toEqual([ + ['baz', 'Baz'], + ['foo', 'Foo'], + ['quz', 'Quz'] + ]) + }) +}) diff --git a/packages/nextra/src/plugin.ts b/packages/nextra/src/plugin.ts index 655c7a47df..a7a114e972 100644 --- a/packages/nextra/src/plugin.ts +++ b/packages/nextra/src/plugin.ts @@ -10,7 +10,7 @@ import { } from './types' import fs from 'graceful-fs' import { promisify } from 'node:util' -import { parseFileName, parseJsonFile, truthy } from './utils' +import { parseFileName, parseJsonFile, sortPages, truthy } from './utils' import path from 'node:path' import slash from 'slash' import grayMatter from 'gray-matter' @@ -98,23 +98,24 @@ export async function collectFiles( const items = (await Promise.all(promises)).filter(truthy) const mdxPages = items.filter( - (item): item is MdxFile => item.kind === 'MdxPage' + (item): item is MdxFile | Folder => + item.kind === 'MdxPage' || item.kind === 'Folder' ) - const locales = mdxPages.map(item => item.locale) + const locales = mdxPages + .filter((item): item is MdxFile => item.kind === 'MdxPage') + .map(item => item.locale) for (const locale of locales) { const metaIndex = items.findIndex( item => item.kind === 'Meta' && item.locale === locale ) - const defaultMeta: [string, string][] = mdxPages - .filter(item => item.locale === locale) - .map(item => [ - item.name, - item.frontMatter?.title || title(item.name.replace(/[-_]/g, ' ')) - ]) + + const defaultMeta = sortPages(mdxPages, locale) + const metaFilename = locale ? META_FILENAME.replace('.', `.${locale}.`) : META_FILENAME + const metaPath = path.join(dir, metaFilename) as MetaJsonPath if (metaIndex === -1) { diff --git a/packages/nextra/src/utils.ts b/packages/nextra/src/utils.ts index 097995a35f..5a907af300 100644 --- a/packages/nextra/src/utils.ts +++ b/packages/nextra/src/utils.ts @@ -1,6 +1,7 @@ import path from 'node:path' +import title from 'title' import { LOCALE_REGEX } from './constants' -import { Meta } from './types' +import { Folder, MdxFile, Meta } from './types' export function parseFileName(filePath: string): { name: string @@ -41,3 +42,34 @@ export function truthy(value: T): value is Truthy { export function normalizeMeta(meta: Meta): Exclude { return typeof meta === 'string' ? { title: meta } : meta } + +export function sortPages( + pages: ( + | Pick + | Pick + )[], + locale?: string +): [string, string][] { + return pages + .filter(item => item.kind === 'Folder' || item.locale === locale) + .map(item => ({ + name: item.name, + date: 'frontMatter' in item && item.frontMatter?.date, + title: + ('frontMatter' in item && item.frontMatter?.title) || + title(item.name.replace(/[-_]/g, ' ')) + })) + .sort((a, b) => { + if (a.date && b.date) { + return new Date(b.date).getTime() - new Date(a.date).getTime() + } + if (a.date) { + return -1 // sort a before b + } + if (b.date) { + return 1 // sort a after b + } + return a.title.localeCompare(b.title, locale, { numeric: true }) + }) + .map(item => [item.name, item.title]) +}