diff --git a/.gitignore b/.gitignore index 657842b..ef88793 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ node_modules/ .pnp.js # Build output -dist/ build/ *.tsbuildinfo diff --git a/dist/components/index.d.ts b/dist/components/index.d.ts new file mode 100644 index 0000000..d1f4911 --- /dev/null +++ b/dist/components/index.d.ts @@ -0,0 +1,2 @@ +export { BreadcrumbOptions, Breadcrumbs } from '../index.js'; +import '@quartz-community/types'; diff --git a/dist/components/index.js b/dist/components/index.js new file mode 100644 index 0000000..f0d6c6d --- /dev/null +++ b/dist/components/index.js @@ -0,0 +1,196 @@ +import { simplifySlug as simplifySlug$1, joinSegments } from '@quartz-community/utils'; +import { jsx, jsxs } from 'preact/jsx-runtime'; + +// src/util/lang.ts +function classNames(...classes) { + return classes.filter(Boolean).join(" "); +} +function simplifySlug(fp) { + return simplifySlug$1(fp); +} +function resolveRelative(current, target) { + const simplified = simplifySlug(target); + const rootPath = pathToRoot(current); + return joinSegments(rootPath, simplified); +} +function pathToRoot(slug) { + let rootPath = slug.split("/").filter((x) => x !== "").slice(0, -1).map((_) => "..").join("/"); + if (rootPath.length === 0) { + rootPath = "."; + } + return rootPath; +} +var FileTrieNode = class _FileTrieNode { + isFolder; + children; + slugSegments; + fileSegmentHint; + displayNameOverride; + data; + constructor(segments, data) { + this.children = []; + this.slugSegments = segments; + this.data = data ?? null; + this.isFolder = false; + this.displayNameOverride = void 0; + } + get displayName() { + const nonIndexTitle = this.data?.title === "index" ? void 0 : this.data?.title; + return this.displayNameOverride ?? nonIndexTitle ?? this.fileSegmentHint ?? this.slugSegment ?? ""; + } + set displayName(name) { + this.displayNameOverride = name; + } + get slug() { + const path = joinSegments(...this.slugSegments); + if (this.isFolder) { + return joinSegments(path, "index"); + } + return path; + } + get slugSegment() { + return this.slugSegments[this.slugSegments.length - 1] ?? ""; + } + makeChild(path, file) { + const nextSegment = path[0]; + if (!nextSegment) { + throw new Error("path is empty"); + } + const fullPath = [...this.slugSegments, nextSegment]; + const child = new _FileTrieNode(fullPath, file); + this.children.push(child); + return child; + } + insert(path, file) { + if (path.length === 0) { + throw new Error("path is empty"); + } + this.isFolder = true; + const segment = path[0]; + if (!segment) { + throw new Error("path is empty"); + } + if (path.length === 1) { + if (segment === "index") { + this.data ??= file; + } else { + this.makeChild(path, file); + } + } else if (path.length > 1) { + const child = this.children.find((c) => c.slugSegment === segment) ?? this.makeChild(path, void 0); + const fileParts = file.filePath.split("/"); + const hint = fileParts.at(-path.length); + if (hint) { + child.fileSegmentHint = hint; + } + child.insert(path.slice(1), file); + } + } + add(file) { + this.insert(file.slug.split("/"), file); + } + findNode(path) { + if (path.length === 0 || path.length === 1 && path[0] === "index") { + return this; + } + return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1)); + } + ancestryChain(path) { + if (path.length === 0 || path.length === 1 && path[0] === "index") { + return [this]; + } + const child = this.children.find((c) => c.slugSegment === path[0]); + if (!child) { + return void 0; + } + const childPath = child.ancestryChain(path.slice(1)); + if (!childPath) { + return void 0; + } + return [this, ...childPath]; + } + filter(filterFn) { + this.children = this.children.filter(filterFn); + this.children.forEach((child) => child.filter(filterFn)); + } + map(mapFn) { + mapFn(this); + this.children.forEach((child) => child.map(mapFn)); + } + sort(sortFn) { + this.children = this.children.sort(sortFn); + this.children.forEach((e) => e.sort(sortFn)); + } +}; +function trieFromAllFiles(allFiles) { + const trie = new FileTrieNode([]); + allFiles.forEach((file) => { + if (file.frontmatter) { + trie.add({ + slug: file.slug, + title: file.frontmatter.title ?? "", + filePath: file.filePath + }); + } + }); + return trie; +} + +// src/components/styles/breadcrumbs.scss +var breadcrumbs_default = ".breadcrumb-container {\n margin: 0;\n margin-top: 0.75rem;\n padding: 0;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.breadcrumb-element p {\n margin: 0;\n margin-left: 0.5rem;\n padding: 0;\n line-height: normal;\n}\n.breadcrumb-element {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n}"; +var defaultOptions = { + spacerSymbol: "\u276F", + rootName: "Home", + resolveFrontmatterTitle: true, + showCurrentPage: true +}; +function formatCrumb(displayName, baseSlug, currentSlug) { + return { + displayName: displayName.replaceAll("-", " "), + path: resolveRelative(baseSlug, currentSlug) + }; +} +var Breadcrumbs_default = ((opts) => { + const options = { ...defaultOptions, ...opts }; + const Breadcrumbs = ({ + fileData, + allFiles, + displayClass, + ctx + }) => { + const typedCtx = ctx ?? {}; + typedCtx.trie ??= trieFromAllFiles( + allFiles + ); + const trie = typedCtx.trie; + const slug = fileData.slug; + const slugParts = slug.split("/"); + const pathNodes = trie.ancestryChain(slugParts); + if (!pathNodes) { + return null; + } + const crumbs = pathNodes.map((node, idx) => { + const crumb = formatCrumb(node.displayName, slug, simplifySlug(node.slug)); + if (idx === 0) { + crumb.displayName = options.rootName; + } + if (idx === pathNodes.length - 1) { + crumb.path = ""; + } + return crumb; + }); + if (!options.showCurrentPage) { + crumbs.pop(); + } + return /* @__PURE__ */ jsx("nav", { class: classNames(displayClass, "breadcrumb-container"), "aria-label": "breadcrumbs", children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs("div", { class: "breadcrumb-element", children: [ + /* @__PURE__ */ jsx("a", { href: crumb.path, children: crumb.displayName }), + index !== crumbs.length - 1 && /* @__PURE__ */ jsx("p", { children: ` ${options.spacerSymbol} ` }) + ] })) }); + }; + Breadcrumbs.css = breadcrumbs_default; + return Breadcrumbs; +}); + +export { Breadcrumbs_default as Breadcrumbs }; +//# sourceMappingURL=index.js.map +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/components/index.js.map b/dist/components/index.js.map new file mode 100644 index 0000000..71e3cce --- /dev/null +++ b/dist/components/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/util/lang.ts","../../src/util/path.ts","../../src/util/fileTrie.ts","../../src/components/styles/breadcrumbs.scss","../../src/components/Breadcrumbs.tsx"],"names":["utilSimplifySlug","joinSegments"],"mappings":"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","file":"index.js","sourcesContent":["export function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","import { simplifySlug as utilSimplifySlug, joinSegments } from \"@quartz-community/utils\";\n\nexport function simplifySlug(fp: string): string {\n return utilSimplifySlug(fp);\n}\n\nexport function resolveRelative(current: string, target: string): string {\n const simplified = simplifySlug(target);\n const rootPath = pathToRoot(current);\n return joinSegments(rootPath, simplified);\n}\n\nfunction pathToRoot(slug: string): string {\n let rootPath = slug\n .split(\"/\")\n .filter((x) => x !== \"\")\n .slice(0, -1)\n .map((_) => \"..\")\n .join(\"/\");\n\n if (rootPath.length === 0) {\n rootPath = \".\";\n }\n\n return rootPath;\n}\n","import { joinSegments } from \"@quartz-community/utils\";\n\ninterface FileTrieData {\n slug: string;\n title: string;\n filePath: string;\n}\n\nexport class FileTrieNode {\n isFolder: boolean;\n children: Array>;\n\n private slugSegments: string[];\n private fileSegmentHint?: string;\n private displayNameOverride?: string;\n data: T | null;\n\n constructor(segments: string[], data?: T) {\n this.children = [];\n this.slugSegments = segments;\n this.data = data ?? null;\n this.isFolder = false;\n this.displayNameOverride = undefined;\n }\n\n get displayName(): string {\n const nonIndexTitle = this.data?.title === \"index\" ? undefined : this.data?.title;\n return (\n this.displayNameOverride ?? nonIndexTitle ?? this.fileSegmentHint ?? this.slugSegment ?? \"\"\n );\n }\n\n set displayName(name: string) {\n this.displayNameOverride = name;\n }\n\n get slug(): string {\n const path = joinSegments(...this.slugSegments);\n if (this.isFolder) {\n return joinSegments(path, \"index\");\n }\n\n return path;\n }\n\n get slugSegment(): string {\n return this.slugSegments[this.slugSegments.length - 1] ?? \"\";\n }\n\n private makeChild(path: string[], file?: T): FileTrieNode {\n const nextSegment = path[0];\n if (!nextSegment) {\n throw new Error(\"path is empty\");\n }\n const fullPath = [...this.slugSegments, nextSegment];\n const child = new FileTrieNode(fullPath, file);\n this.children.push(child);\n return child;\n }\n\n private insert(path: string[], file: T): void {\n if (path.length === 0) {\n throw new Error(\"path is empty\");\n }\n\n this.isFolder = true;\n const segment = path[0];\n if (!segment) {\n throw new Error(\"path is empty\");\n }\n if (path.length === 1) {\n if (segment === \"index\") {\n this.data ??= file;\n } else {\n this.makeChild(path, file);\n }\n } else if (path.length > 1) {\n const child =\n this.children.find((c) => c.slugSegment === segment) ?? this.makeChild(path, undefined);\n\n const fileParts = file.filePath.split(\"/\");\n const hint = fileParts.at(-path.length);\n if (hint) {\n child.fileSegmentHint = hint;\n }\n child.insert(path.slice(1), file);\n }\n }\n\n add(file: T): void {\n this.insert(file.slug.split(\"/\"), file);\n }\n\n findNode(path: string[]): FileTrieNode | undefined {\n if (path.length === 0 || (path.length === 1 && path[0] === \"index\")) {\n return this;\n }\n\n return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1));\n }\n\n ancestryChain(path: string[]): Array> | undefined {\n if (path.length === 0 || (path.length === 1 && path[0] === \"index\")) {\n return [this];\n }\n\n const child = this.children.find((c) => c.slugSegment === path[0]);\n if (!child) {\n return undefined;\n }\n\n const childPath = child.ancestryChain(path.slice(1));\n if (!childPath) {\n return undefined;\n }\n\n return [this, ...childPath];\n }\n\n filter(filterFn: (node: FileTrieNode) => boolean): void {\n this.children = this.children.filter(filterFn);\n this.children.forEach((child) => child.filter(filterFn));\n }\n\n map(mapFn: (node: FileTrieNode) => void): void {\n mapFn(this);\n this.children.forEach((child) => child.map(mapFn));\n }\n\n sort(sortFn: (a: FileTrieNode, b: FileTrieNode) => number): void {\n this.children = this.children.sort(sortFn);\n this.children.forEach((e) => e.sort(sortFn));\n }\n}\n\nexport function trieFromAllFiles(\n allFiles: Array<{\n slug?: string;\n filePath?: string;\n frontmatter?: { title?: string; [key: string]: unknown };\n }>,\n): FileTrieNode {\n const trie = new FileTrieNode([]);\n allFiles.forEach((file) => {\n if (file.frontmatter) {\n trie.add({\n slug: file.slug!,\n title: file.frontmatter.title ?? \"\",\n filePath: file.filePath!,\n });\n }\n });\n\n return trie;\n}\n",".breadcrumb-container {\n margin: 0;\n margin-top: 0.75rem;\n padding: 0;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.breadcrumb-element p {\n margin: 0;\n margin-left: 0.5rem;\n padding: 0;\n line-height: normal;\n}\n.breadcrumb-element {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n}","import type { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\nimport { resolveRelative, simplifySlug } from \"../util/path\";\nimport { FileTrieNode, trieFromAllFiles } from \"../util/fileTrie\";\nimport style from \"./styles/breadcrumbs.scss\";\n\ntype QuartzComponentConstructor = (\n opts: Options,\n) => QuartzComponent;\n\ntype CrumbData = {\n displayName: string;\n path: string;\n};\n\nexport interface BreadcrumbOptions {\n /** Symbol between crumbs */\n spacerSymbol: string;\n /** Name of first crumb */\n rootName: string;\n /** Whether to look up frontmatter title for folders */\n resolveFrontmatterTitle: boolean;\n /** Whether to display the current page in the breadcrumbs */\n showCurrentPage: boolean;\n}\n\nconst defaultOptions: BreadcrumbOptions = {\n spacerSymbol: \"❯\",\n rootName: \"Home\",\n resolveFrontmatterTitle: true,\n showCurrentPage: true,\n};\n\nfunction formatCrumb(displayName: string, baseSlug: string, currentSlug: string): CrumbData {\n return {\n displayName: displayName.replaceAll(\"-\", \" \"),\n path: resolveRelative(baseSlug, currentSlug),\n };\n}\n\nexport default ((opts?: Partial) => {\n const options: BreadcrumbOptions = { ...defaultOptions, ...opts };\n const Breadcrumbs: QuartzComponent = ({\n fileData,\n allFiles,\n displayClass,\n ctx,\n }: QuartzComponentProps) => {\n const typedCtx = (ctx ?? {}) as Record as { trie?: FileTrieNode };\n typedCtx.trie ??= trieFromAllFiles(\n allFiles as Array<{\n slug?: string;\n filePath?: string;\n frontmatter?: { title?: string; [key: string]: unknown };\n }>,\n );\n const trie = typedCtx.trie;\n const slug = fileData.slug as string;\n const slugParts = slug.split(\"/\");\n const pathNodes = trie.ancestryChain(slugParts);\n\n if (!pathNodes) {\n return null;\n }\n\n const crumbs: CrumbData[] = pathNodes.map((node, idx) => {\n const crumb = formatCrumb(node.displayName, slug, simplifySlug(node.slug));\n if (idx === 0) {\n crumb.displayName = options.rootName;\n }\n\n if (idx === pathNodes.length - 1) {\n crumb.path = \"\";\n }\n\n return crumb;\n });\n\n if (!options.showCurrentPage) {\n crumbs.pop();\n }\n\n return (\n \n );\n };\n Breadcrumbs.css = style;\n\n return Breadcrumbs;\n}) satisfies QuartzComponentConstructor;\n"]} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..e8be288 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,16 @@ +import { QuartzComponent } from '@quartz-community/types'; +export { QuartzComponent, QuartzComponentProps, StringResource } from '@quartz-community/types'; + +interface BreadcrumbOptions { + /** Symbol between crumbs */ + spacerSymbol: string; + /** Name of first crumb */ + rootName: string; + /** Whether to look up frontmatter title for folders */ + resolveFrontmatterTitle: boolean; + /** Whether to display the current page in the breadcrumbs */ + showCurrentPage: boolean; +} +declare const _default: (opts?: Partial) => QuartzComponent; + +export { type BreadcrumbOptions, _default as Breadcrumbs }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..f0d6c6d --- /dev/null +++ b/dist/index.js @@ -0,0 +1,196 @@ +import { simplifySlug as simplifySlug$1, joinSegments } from '@quartz-community/utils'; +import { jsx, jsxs } from 'preact/jsx-runtime'; + +// src/util/lang.ts +function classNames(...classes) { + return classes.filter(Boolean).join(" "); +} +function simplifySlug(fp) { + return simplifySlug$1(fp); +} +function resolveRelative(current, target) { + const simplified = simplifySlug(target); + const rootPath = pathToRoot(current); + return joinSegments(rootPath, simplified); +} +function pathToRoot(slug) { + let rootPath = slug.split("/").filter((x) => x !== "").slice(0, -1).map((_) => "..").join("/"); + if (rootPath.length === 0) { + rootPath = "."; + } + return rootPath; +} +var FileTrieNode = class _FileTrieNode { + isFolder; + children; + slugSegments; + fileSegmentHint; + displayNameOverride; + data; + constructor(segments, data) { + this.children = []; + this.slugSegments = segments; + this.data = data ?? null; + this.isFolder = false; + this.displayNameOverride = void 0; + } + get displayName() { + const nonIndexTitle = this.data?.title === "index" ? void 0 : this.data?.title; + return this.displayNameOverride ?? nonIndexTitle ?? this.fileSegmentHint ?? this.slugSegment ?? ""; + } + set displayName(name) { + this.displayNameOverride = name; + } + get slug() { + const path = joinSegments(...this.slugSegments); + if (this.isFolder) { + return joinSegments(path, "index"); + } + return path; + } + get slugSegment() { + return this.slugSegments[this.slugSegments.length - 1] ?? ""; + } + makeChild(path, file) { + const nextSegment = path[0]; + if (!nextSegment) { + throw new Error("path is empty"); + } + const fullPath = [...this.slugSegments, nextSegment]; + const child = new _FileTrieNode(fullPath, file); + this.children.push(child); + return child; + } + insert(path, file) { + if (path.length === 0) { + throw new Error("path is empty"); + } + this.isFolder = true; + const segment = path[0]; + if (!segment) { + throw new Error("path is empty"); + } + if (path.length === 1) { + if (segment === "index") { + this.data ??= file; + } else { + this.makeChild(path, file); + } + } else if (path.length > 1) { + const child = this.children.find((c) => c.slugSegment === segment) ?? this.makeChild(path, void 0); + const fileParts = file.filePath.split("/"); + const hint = fileParts.at(-path.length); + if (hint) { + child.fileSegmentHint = hint; + } + child.insert(path.slice(1), file); + } + } + add(file) { + this.insert(file.slug.split("/"), file); + } + findNode(path) { + if (path.length === 0 || path.length === 1 && path[0] === "index") { + return this; + } + return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1)); + } + ancestryChain(path) { + if (path.length === 0 || path.length === 1 && path[0] === "index") { + return [this]; + } + const child = this.children.find((c) => c.slugSegment === path[0]); + if (!child) { + return void 0; + } + const childPath = child.ancestryChain(path.slice(1)); + if (!childPath) { + return void 0; + } + return [this, ...childPath]; + } + filter(filterFn) { + this.children = this.children.filter(filterFn); + this.children.forEach((child) => child.filter(filterFn)); + } + map(mapFn) { + mapFn(this); + this.children.forEach((child) => child.map(mapFn)); + } + sort(sortFn) { + this.children = this.children.sort(sortFn); + this.children.forEach((e) => e.sort(sortFn)); + } +}; +function trieFromAllFiles(allFiles) { + const trie = new FileTrieNode([]); + allFiles.forEach((file) => { + if (file.frontmatter) { + trie.add({ + slug: file.slug, + title: file.frontmatter.title ?? "", + filePath: file.filePath + }); + } + }); + return trie; +} + +// src/components/styles/breadcrumbs.scss +var breadcrumbs_default = ".breadcrumb-container {\n margin: 0;\n margin-top: 0.75rem;\n padding: 0;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.breadcrumb-element p {\n margin: 0;\n margin-left: 0.5rem;\n padding: 0;\n line-height: normal;\n}\n.breadcrumb-element {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n}"; +var defaultOptions = { + spacerSymbol: "\u276F", + rootName: "Home", + resolveFrontmatterTitle: true, + showCurrentPage: true +}; +function formatCrumb(displayName, baseSlug, currentSlug) { + return { + displayName: displayName.replaceAll("-", " "), + path: resolveRelative(baseSlug, currentSlug) + }; +} +var Breadcrumbs_default = ((opts) => { + const options = { ...defaultOptions, ...opts }; + const Breadcrumbs = ({ + fileData, + allFiles, + displayClass, + ctx + }) => { + const typedCtx = ctx ?? {}; + typedCtx.trie ??= trieFromAllFiles( + allFiles + ); + const trie = typedCtx.trie; + const slug = fileData.slug; + const slugParts = slug.split("/"); + const pathNodes = trie.ancestryChain(slugParts); + if (!pathNodes) { + return null; + } + const crumbs = pathNodes.map((node, idx) => { + const crumb = formatCrumb(node.displayName, slug, simplifySlug(node.slug)); + if (idx === 0) { + crumb.displayName = options.rootName; + } + if (idx === pathNodes.length - 1) { + crumb.path = ""; + } + return crumb; + }); + if (!options.showCurrentPage) { + crumbs.pop(); + } + return /* @__PURE__ */ jsx("nav", { class: classNames(displayClass, "breadcrumb-container"), "aria-label": "breadcrumbs", children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs("div", { class: "breadcrumb-element", children: [ + /* @__PURE__ */ jsx("a", { href: crumb.path, children: crumb.displayName }), + index !== crumbs.length - 1 && /* @__PURE__ */ jsx("p", { children: ` ${options.spacerSymbol} ` }) + ] })) }); + }; + Breadcrumbs.css = breadcrumbs_default; + return Breadcrumbs; +}); + +export { Breadcrumbs_default as Breadcrumbs }; +//# sourceMappingURL=index.js.map +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..89064a9 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/util/lang.ts","../src/util/path.ts","../src/util/fileTrie.ts","../src/components/styles/breadcrumbs.scss","../src/components/Breadcrumbs.tsx"],"names":["utilSimplifySlug","joinSegments"],"mappings":"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","file":"index.js","sourcesContent":["export function classNames(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n","import { simplifySlug as utilSimplifySlug, joinSegments } from \"@quartz-community/utils\";\n\nexport function simplifySlug(fp: string): string {\n return utilSimplifySlug(fp);\n}\n\nexport function resolveRelative(current: string, target: string): string {\n const simplified = simplifySlug(target);\n const rootPath = pathToRoot(current);\n return joinSegments(rootPath, simplified);\n}\n\nfunction pathToRoot(slug: string): string {\n let rootPath = slug\n .split(\"/\")\n .filter((x) => x !== \"\")\n .slice(0, -1)\n .map((_) => \"..\")\n .join(\"/\");\n\n if (rootPath.length === 0) {\n rootPath = \".\";\n }\n\n return rootPath;\n}\n","import { joinSegments } from \"@quartz-community/utils\";\n\ninterface FileTrieData {\n slug: string;\n title: string;\n filePath: string;\n}\n\nexport class FileTrieNode {\n isFolder: boolean;\n children: Array>;\n\n private slugSegments: string[];\n private fileSegmentHint?: string;\n private displayNameOverride?: string;\n data: T | null;\n\n constructor(segments: string[], data?: T) {\n this.children = [];\n this.slugSegments = segments;\n this.data = data ?? null;\n this.isFolder = false;\n this.displayNameOverride = undefined;\n }\n\n get displayName(): string {\n const nonIndexTitle = this.data?.title === \"index\" ? undefined : this.data?.title;\n return (\n this.displayNameOverride ?? nonIndexTitle ?? this.fileSegmentHint ?? this.slugSegment ?? \"\"\n );\n }\n\n set displayName(name: string) {\n this.displayNameOverride = name;\n }\n\n get slug(): string {\n const path = joinSegments(...this.slugSegments);\n if (this.isFolder) {\n return joinSegments(path, \"index\");\n }\n\n return path;\n }\n\n get slugSegment(): string {\n return this.slugSegments[this.slugSegments.length - 1] ?? \"\";\n }\n\n private makeChild(path: string[], file?: T): FileTrieNode {\n const nextSegment = path[0];\n if (!nextSegment) {\n throw new Error(\"path is empty\");\n }\n const fullPath = [...this.slugSegments, nextSegment];\n const child = new FileTrieNode(fullPath, file);\n this.children.push(child);\n return child;\n }\n\n private insert(path: string[], file: T): void {\n if (path.length === 0) {\n throw new Error(\"path is empty\");\n }\n\n this.isFolder = true;\n const segment = path[0];\n if (!segment) {\n throw new Error(\"path is empty\");\n }\n if (path.length === 1) {\n if (segment === \"index\") {\n this.data ??= file;\n } else {\n this.makeChild(path, file);\n }\n } else if (path.length > 1) {\n const child =\n this.children.find((c) => c.slugSegment === segment) ?? this.makeChild(path, undefined);\n\n const fileParts = file.filePath.split(\"/\");\n const hint = fileParts.at(-path.length);\n if (hint) {\n child.fileSegmentHint = hint;\n }\n child.insert(path.slice(1), file);\n }\n }\n\n add(file: T): void {\n this.insert(file.slug.split(\"/\"), file);\n }\n\n findNode(path: string[]): FileTrieNode | undefined {\n if (path.length === 0 || (path.length === 1 && path[0] === \"index\")) {\n return this;\n }\n\n return this.children.find((c) => c.slugSegment === path[0])?.findNode(path.slice(1));\n }\n\n ancestryChain(path: string[]): Array> | undefined {\n if (path.length === 0 || (path.length === 1 && path[0] === \"index\")) {\n return [this];\n }\n\n const child = this.children.find((c) => c.slugSegment === path[0]);\n if (!child) {\n return undefined;\n }\n\n const childPath = child.ancestryChain(path.slice(1));\n if (!childPath) {\n return undefined;\n }\n\n return [this, ...childPath];\n }\n\n filter(filterFn: (node: FileTrieNode) => boolean): void {\n this.children = this.children.filter(filterFn);\n this.children.forEach((child) => child.filter(filterFn));\n }\n\n map(mapFn: (node: FileTrieNode) => void): void {\n mapFn(this);\n this.children.forEach((child) => child.map(mapFn));\n }\n\n sort(sortFn: (a: FileTrieNode, b: FileTrieNode) => number): void {\n this.children = this.children.sort(sortFn);\n this.children.forEach((e) => e.sort(sortFn));\n }\n}\n\nexport function trieFromAllFiles(\n allFiles: Array<{\n slug?: string;\n filePath?: string;\n frontmatter?: { title?: string; [key: string]: unknown };\n }>,\n): FileTrieNode {\n const trie = new FileTrieNode([]);\n allFiles.forEach((file) => {\n if (file.frontmatter) {\n trie.add({\n slug: file.slug!,\n title: file.frontmatter.title ?? \"\",\n filePath: file.filePath!,\n });\n }\n });\n\n return trie;\n}\n",".breadcrumb-container {\n margin: 0;\n margin-top: 0.75rem;\n padding: 0;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.breadcrumb-element p {\n margin: 0;\n margin-left: 0.5rem;\n padding: 0;\n line-height: normal;\n}\n.breadcrumb-element {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n}","import type { QuartzComponent, QuartzComponentProps } from \"@quartz-community/types\";\nimport { classNames } from \"../util/lang\";\nimport { resolveRelative, simplifySlug } from \"../util/path\";\nimport { FileTrieNode, trieFromAllFiles } from \"../util/fileTrie\";\nimport style from \"./styles/breadcrumbs.scss\";\n\ntype QuartzComponentConstructor = (\n opts: Options,\n) => QuartzComponent;\n\ntype CrumbData = {\n displayName: string;\n path: string;\n};\n\nexport interface BreadcrumbOptions {\n /** Symbol between crumbs */\n spacerSymbol: string;\n /** Name of first crumb */\n rootName: string;\n /** Whether to look up frontmatter title for folders */\n resolveFrontmatterTitle: boolean;\n /** Whether to display the current page in the breadcrumbs */\n showCurrentPage: boolean;\n}\n\nconst defaultOptions: BreadcrumbOptions = {\n spacerSymbol: \"❯\",\n rootName: \"Home\",\n resolveFrontmatterTitle: true,\n showCurrentPage: true,\n};\n\nfunction formatCrumb(displayName: string, baseSlug: string, currentSlug: string): CrumbData {\n return {\n displayName: displayName.replaceAll(\"-\", \" \"),\n path: resolveRelative(baseSlug, currentSlug),\n };\n}\n\nexport default ((opts?: Partial) => {\n const options: BreadcrumbOptions = { ...defaultOptions, ...opts };\n const Breadcrumbs: QuartzComponent = ({\n fileData,\n allFiles,\n displayClass,\n ctx,\n }: QuartzComponentProps) => {\n const typedCtx = (ctx ?? {}) as Record as { trie?: FileTrieNode };\n typedCtx.trie ??= trieFromAllFiles(\n allFiles as Array<{\n slug?: string;\n filePath?: string;\n frontmatter?: { title?: string; [key: string]: unknown };\n }>,\n );\n const trie = typedCtx.trie;\n const slug = fileData.slug as string;\n const slugParts = slug.split(\"/\");\n const pathNodes = trie.ancestryChain(slugParts);\n\n if (!pathNodes) {\n return null;\n }\n\n const crumbs: CrumbData[] = pathNodes.map((node, idx) => {\n const crumb = formatCrumb(node.displayName, slug, simplifySlug(node.slug));\n if (idx === 0) {\n crumb.displayName = options.rootName;\n }\n\n if (idx === pathNodes.length - 1) {\n crumb.path = \"\";\n }\n\n return crumb;\n });\n\n if (!options.showCurrentPage) {\n crumbs.pop();\n }\n\n return (\n \n );\n };\n Breadcrumbs.css = style;\n\n return Breadcrumbs;\n}) satisfies QuartzComponentConstructor;\n"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 26f80f4..33edf43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,7 @@ "license": "MIT", "dependencies": { "@quartz-community/types": "github:quartz-community/types", - "@quartz-community/utils": "github:quartz-community/utils", - "tsup": "^8.5.0", - "typescript": "^5.9.3" + "@quartz-community/utils": "github:quartz-community/utils" }, "devDependencies": { "@types/hast": "^3.0.4", @@ -26,6 +24,8 @@ "prettier": "^3.6.2", "sass": "^1.97.3", "sass-embedded": "^1.79.5", + "tsup": "^8.5.0", + "typescript": "^5.9.3", "vfile": "^6.0.3", "vitest": "^2.1.9" }, @@ -4061,7 +4061,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", diff --git a/package.json b/package.json index 3d3c75f..9c3e53a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "sideEffects": false, "scripts": { "build": "tsup", - "prepare": "npm run build", "dev": "tsup --watch", "lint": "eslint . --max-warnings=0", "format": "prettier . --check",