Breadcrumbs (#296)
* add breadcrumbs * rm space * move breadcrumbs to ui
This commit is contained in:
1 parent
ad9695127a
commit
98dcaaa237
11 files changed
+92
-12
No files matched your search
@@ -5,12 +5,11 @@ import s from "./app.module.css"
|
|||||||
import { Wrapper } from "../Wrapper"
|
import { Wrapper } from "../Wrapper"
|
||||||
import { Footer } from "../Footer"
|
import { Footer } from "../Footer"
|
||||||
import { Content } from "../Content"
|
import { Content } from "../Content"
|
||||||
import { Breadcrumbs } from "../Breadcrumbs"
|
|
||||||
import type { BreadcrumbPart } from "@/types"
|
import type { BreadcrumbPart } from "@/types"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useMobile } from "@hudozka/hooks"
|
import { useMobile } from "@hudozka/hooks"
|
||||||
import { Navigation } from "@/components/Navigation"
|
import { Navigation } from "@/components/Navigation"
|
||||||
import { Box, Panel, Title } from "@hudozka/ui"
|
import { Box, Breadcrumbs, Panel, Title } from "@hudozka/ui"
|
||||||
import { AccessibilityButton } from "../AccessibilityButton"
|
import { AccessibilityButton } from "../AccessibilityButton"
|
||||||
import { Copyright } from "@/components/Copyright"
|
import { Copyright } from "@/components/Copyright"
|
||||||
import { yearRange } from "@/const"
|
import { yearRange } from "@/const"
|
||||||
|
|||||||
@@ -2,7 +2,43 @@ import { pb } from "./pb"
|
|||||||
import { createPage, createHomeCards, createMenu, createFeedPages } from "./factory"
|
import { createPage, createHomeCards, createMenu, createFeedPages } from "./factory"
|
||||||
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
|
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
|
||||||
import type { DocV1Block } from "./doc"
|
import type { DocV1Block } from "./doc"
|
||||||
import type { MenuItem, Page, PageCardDto, FeedPage } from "@/types"
|
import type { BreadcrumbPart, MenuItem, Page, PageCardDto, FeedPage } from "@/types"
|
||||||
|
import { menu } from "@/const"
|
||||||
|
|
||||||
|
const MAX_PARENT_DEPTH = 10
|
||||||
|
|
||||||
|
function getParentId(parent: string | string[] | undefined): string | undefined {
|
||||||
|
if (!parent) return undefined
|
||||||
|
if (Array.isArray(parent)) return parent[0]
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchParentChain(startId: string | undefined): Promise<PbPage[]> {
|
||||||
|
if (!startId) return []
|
||||||
|
const chain: PbPage[] = []
|
||||||
|
const visited = new Set<string>()
|
||||||
|
let currentId: string | undefined = startId
|
||||||
|
while (currentId && chain.length < MAX_PARENT_DEPTH) {
|
||||||
|
if (visited.has(currentId)) break
|
||||||
|
visited.add(currentId)
|
||||||
|
try {
|
||||||
|
const parent = await pb.collection("pages").getOne<PbPage>(currentId)
|
||||||
|
chain.unshift(parent)
|
||||||
|
currentId = getParentId(parent.parent)
|
||||||
|
} catch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBreadcrumb(record: PbPage, parents: PbPage[]): BreadcrumbPart[] {
|
||||||
|
if (parents.length === 0) return []
|
||||||
|
const home: BreadcrumbPart = { name: menu[0].name, href: "/" }
|
||||||
|
const ancestors: BreadcrumbPart[] = parents.map(p => ({ name: p.title, href: p.slug }))
|
||||||
|
const current: BreadcrumbPart = { name: record.title, href: record.slug }
|
||||||
|
return [home, ...ancestors, current]
|
||||||
|
}
|
||||||
|
|
||||||
function buildIdFilter(ids: string[]): string {
|
function buildIdFilter(ids: string[]): string {
|
||||||
return ids.map(id => `id="${id}"`).join(" || ")
|
return ids.map(id => `id="${id}"`).join(" || ")
|
||||||
@@ -90,12 +126,13 @@ export async function getPageBySlug(slug: string): Promise<Page | null> {
|
|||||||
refs.imageIds.add(record.cover)
|
refs.imageIds.add(record.cover)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round 2: fetch images, files, tags, card-grid pages in parallel
|
// Round 2: fetch images, files, tags, card-grid pages, parent chain in parallel
|
||||||
const [images, files, tags, cardGridPages] = await Promise.all([
|
const [images, files, tags, cardGridPages, parentChain] = await Promise.all([
|
||||||
fetchImagesByIds([...refs.imageIds]),
|
fetchImagesByIds([...refs.imageIds]),
|
||||||
fetchFilesByIds([...refs.fileIds]),
|
fetchFilesByIds([...refs.fileIds]),
|
||||||
fetchTagsByIds(record.tags),
|
fetchTagsByIds(record.tags),
|
||||||
fetchPagesByIds([...refs.pageIds]),
|
fetchPagesByIds([...refs.pageIds]),
|
||||||
|
fetchParentChain(getParentId(record.parent)),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Round 3: fetch cover images for card-grid pages
|
// Round 3: fetch cover images for card-grid pages
|
||||||
@@ -112,7 +149,8 @@ export async function getPageBySlug(slug: string): Promise<Page | null> {
|
|||||||
// Merge all images for card-grid covers
|
// Merge all images for card-grid covers
|
||||||
const allCardGridImages = new Map([...images, ...cardGridImages])
|
const allCardGridImages = new Map([...images, ...cardGridImages])
|
||||||
|
|
||||||
return createPage(record, images, files, tags, cardGridPages, allCardGridImages)
|
const breadcrumb = buildBreadcrumb(record, parentChain)
|
||||||
|
return createPage(record, images, files, tags, cardGridPages, allCardGridImages, breadcrumb)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch page: ${error}`)
|
console.error(`Failed to fetch page: ${error}`)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MenuItem, Page, Tag, PageCardDto, Pic, Token, FeedPage } from "@/types"
|
import type { MenuItem, Page, Tag, PageCardDto, Pic, Token, FeedPage, BreadcrumbPart } from "@/types"
|
||||||
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
|
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
|
||||||
import type { DocV1Block } from "./doc"
|
import type { DocV1Block } from "./doc"
|
||||||
import { typograf, markdownToHtml } from "@hudozka/text"
|
import { typograf, markdownToHtml } from "@hudozka/text"
|
||||||
@@ -159,6 +159,7 @@ export function createPage(
|
|||||||
tags: PbTag[],
|
tags: PbTag[],
|
||||||
cardGridPages: Map<string, PbPage>,
|
cardGridPages: Map<string, PbPage>,
|
||||||
cardGridImages: Map<string, PbImage>,
|
cardGridImages: Map<string, PbImage>,
|
||||||
|
breadcrumb: BreadcrumbPart[] = [],
|
||||||
): Page {
|
): Page {
|
||||||
const cover = getCoverPic(record.cover, images)
|
const cover = getCoverPic(record.cover, images)
|
||||||
|
|
||||||
@@ -190,6 +191,7 @@ export function createPage(
|
|||||||
...tokens,
|
...tokens,
|
||||||
],
|
],
|
||||||
tags: pageTags,
|
tags: pageTags,
|
||||||
|
breadcrumb,
|
||||||
featured: false,
|
featured: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type PbPage = PbRecord & {
|
|||||||
doc: DocV1
|
doc: DocV1
|
||||||
tags: string[]
|
tags: string[]
|
||||||
draft: boolean
|
draft: boolean
|
||||||
|
parent?: string | string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PbTag = PbRecord & {
|
export type PbTag = PbRecord & {
|
||||||
|
|||||||
Generated
+1
@@ -9633,6 +9633,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hudozka/hooks": "*",
|
"@hudozka/hooks": "*",
|
||||||
|
"@hudozka/utils": "*",
|
||||||
"classnames": "^2.5.0"
|
"classnames": "^2.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.5.0",
|
"classnames": "^2.5.0",
|
||||||
"@hudozka/hooks": "*"
|
"@hudozka/hooks": "*",
|
||||||
|
"@hudozka/utils": "*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=19",
|
"react": ">=19",
|
||||||
|
|||||||
File renamed without changes.
+8
-4
@@ -1,12 +1,16 @@
|
|||||||
import s from "./styles.module.css"
|
import s from "./styles.module.css"
|
||||||
|
|
||||||
import type { BreadcrumbPart } from "@/types"
|
import { Button } from "../Button"
|
||||||
import { Button } from "@hudozka/ui"
|
|
||||||
import { insertBetween } from "@hudozka/utils"
|
import { insertBetween } from "@hudozka/utils"
|
||||||
|
|
||||||
|
export type Crumb = {
|
||||||
|
name: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
export type BreadcrumbsProps = {
|
export type BreadcrumbsProps = {
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
items: BreadcrumbPart[]
|
items: Crumb[]
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +41,6 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = props => {
|
|||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</nav >
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+4
@@ -11,3 +11,7 @@
|
|||||||
.breadcrumbs li {
|
.breadcrumbs li {
|
||||||
margin-bottom: var(--size-xs);
|
margin-bottom: var(--size-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumbs a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./Box"
|
export * from "./Box"
|
||||||
|
export * from "./Breadcrumbs"
|
||||||
export * from "./Button"
|
export * from "./Button"
|
||||||
export * from "./Panel"
|
export * from "./Panel"
|
||||||
export * from "./Title"
|
export * from "./Title"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_1125843985")
|
||||||
|
|
||||||
|
// add field
|
||||||
|
collection.fields.addAt(5, new Field({
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "pbc_1125843985",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation1032740943",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "parent",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_1125843985")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.fields.removeById("relation1032740943")
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user