Sidebars gone: the sidebar data model goes; the masthead tagline falls back to findSectionNote and Blog/index.md owns the blog's line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-02 19:34:58 +02:00
commit 3bcce33071
3 changed files with 7 additions and 271 deletions

View file

@ -3,3 +3,5 @@ title: Blog
description: "Recent GameCult notes, fiction, experiments, and whatever else clawed its way into public before anyone could over-curate it."
socialDeck: "Recent notes, fiction, experiments, and other escaped materials."
---
*"Recent notes, fiction, experiments, and other escaped materials."*

View file

@ -69,8 +69,6 @@ export default ((opts?: Partial<Options>) => {
defaultAuthor: "GameCult",
emptyDescription: "This post exists, which is already more than many ideas manage.",
showDescriptionIntro: true,
sidebarTagline: "Recent notes, fiction, experiments, and other escaped materials.",
sidebarSummary: (count) => `${count} public posts, newest trouble first.`,
})
const GameCultContent: QuartzComponent = (props: QuartzComponentProps) => {

View file

@ -3,43 +3,9 @@ import { toString } from "hast-util-to-string"
import { QuartzPluginData } from "../plugins/vfile"
import { FullSlug, simplifySlug, splitAnchor, stripSlashes } from "../util/path"
import { clone } from "../util/clone"
import { buildAutoIndexSidebarData } from "./AutoIndexFolder"
export type GameCultSidebarLink = {
label: string
slug?: FullSlug
href?: string
external?: boolean
}
export type GameCultSidebarGroup = {
title: string
links: GameCultSidebarLink[]
}
export type GameCultSidebarData = {
title: string
slug: FullSlug
tagline?: string
summary?: string
groups: GameCultSidebarGroup[]
}
export type GameCultPageContext = {
headerTagline?: string
sidebar?: GameCultSidebarData
}
type FrontmatterSidebarLink = {
label?: unknown
slug?: unknown
href?: unknown
external?: unknown
}
type FrontmatterSidebarGroup = {
title?: unknown
links?: unknown
}
type ExtractedTagline = {
@ -131,10 +97,9 @@ function fileBySlug(allFiles: QuartzPluginData[]) {
export function resolveGameCultSourceFile(
currentFile: QuartzPluginData,
allFiles: QuartzPluginData[],
field: "contentSource" | "sidebarSource" = "contentSource",
) {
const currentSlug = currentFile.slug as FullSlug | undefined
const rawValue = currentFile.frontmatter?.[field]
const rawValue = currentFile.frontmatter?.contentSource
if (!currentSlug || typeof rawValue !== "string") {
return undefined
}
@ -151,23 +116,6 @@ function isOverviewSlug(slug: string) {
return slug === "index" || slug.endsWith("/index")
}
export function isSidebarLinkActive(currentSlug: FullSlug, targetSlug: FullSlug) {
const normalizedCurrent = normalizeGameCultSlug(currentSlug)
const normalizedTarget = normalizeGameCultSlug(targetSlug)
if (targetSlug === "index") {
return normalizedCurrent === "index"
}
if (isOverviewSlug(targetSlug)) {
return (
normalizedCurrent === normalizedTarget || normalizedCurrent.startsWith(`${normalizedTarget}/`)
)
}
return normalizedCurrent === normalizedTarget
}
export function extractTopTagline(root?: Root): ExtractedTagline | undefined {
if (!root) {
return undefined
@ -226,187 +174,6 @@ export function stripTopHeading(root?: Root) {
return undefined
}
function resolveLinkTarget(sourceSlug: FullSlug, node: Element) {
const href = node.properties?.href
if (typeof href === "string" && !href.startsWith("#") && !href.startsWith("http")) {
const sourceDir = stripSlashes(simplifySlug(sourceSlug), true)
const url = new URL(href, `https://base.com/${sourceDir}`)
let [targetPath] = splitAnchor(decodeURIComponent(url.pathname))
if (targetPath.endsWith("/")) {
targetPath += "index"
}
const full = stripSlashes(targetPath, true)
if (full.length > 0) {
return full as FullSlug
}
}
const slug = node.properties?.["data-slug"]
if (typeof slug === "string") {
return slug as FullSlug
}
return undefined
}
function collectLinks(
sourceSlug: FullSlug,
node: Element,
dedupe: Set<string>,
links: GameCultSidebarLink[],
) {
if (node.tagName === "a") {
const slug = resolveLinkTarget(sourceSlug, node)
const label = normalizeText(toString(node))
if (slug && label.length > 0 && !dedupe.has(slug)) {
dedupe.add(slug)
links.push({
label,
slug,
})
}
}
for (const child of node.children) {
if (isElement(child)) {
collectLinks(sourceSlug, child, dedupe, links)
}
}
}
function extractOverviewSummary(root: Root, tagline?: ExtractedTagline) {
let titleSkipped = false
for (const { node, index } of topLevelElements(root)) {
if (!titleSkipped && node.tagName === "h1") {
titleSkipped = true
continue
}
if (tagline && index === tagline.nodeIndex) {
continue
}
if (node.tagName === "h2") {
return undefined
}
if (node.tagName === "p") {
const summary = normalizeText(toString(node))
if (summary.length > 0) {
return summary
}
}
}
return undefined
}
function extractOverviewGroups(root: Root, sourceSlug: FullSlug): GameCultSidebarGroup[] {
const groups: GameCultSidebarGroup[] = []
const entries = topLevelElements(root)
for (let i = 0; i < entries.length; i++) {
const current = entries[i].node
if (current.tagName !== "h2") {
continue
}
const title = normalizeText(toString(current))
if (title.length === 0) {
continue
}
const dedupe = new Set<string>()
const links: GameCultSidebarLink[] = []
for (let j = i + 1; j < entries.length; j++) {
const next = entries[j].node
if (next.tagName === "h1" || next.tagName === "h2") {
break
}
if (next.tagName === "ul" || next.tagName === "ol") {
collectLinks(sourceSlug, next, dedupe, links)
}
}
if (links.length > 0) {
groups.push({ title, links })
}
}
return groups
}
function extractFrontmatterOverviewGroups(file: QuartzPluginData): GameCultSidebarGroup[] | undefined {
if (!file.slug || !Array.isArray(file.frontmatter?.sidebarGroups)) {
return undefined
}
const groups = (file.frontmatter.sidebarGroups as FrontmatterSidebarGroup[])
.map((rawGroup) => {
if (
typeof rawGroup !== "object" ||
rawGroup === null ||
typeof rawGroup.title !== "string" ||
!Array.isArray(rawGroup.links)
) {
return undefined
}
const links = (rawGroup.links as FrontmatterSidebarLink[])
.map((rawLink) => {
if (
typeof rawLink !== "object" ||
rawLink === null ||
typeof rawLink.label !== "string" ||
typeof rawLink.slug !== "string"
) {
return undefined
}
let slug: FullSlug | undefined
let href: string | undefined
let external = false
if (typeof rawLink.slug === "string") {
slug = resolveGameCultReferenceSlug(file.slug as FullSlug, rawLink.slug)
if (!slug) {
return undefined
}
} else if (typeof rawLink.href === "string" && rawLink.href.trim().length > 0) {
href = rawLink.href.trim()
external = rawLink.external === true || /^https?:\/\//i.test(href)
} else {
return undefined
}
return {
label: rawLink.label,
slug,
href,
external,
}
})
.filter((link): link is GameCultSidebarLink => link !== undefined)
if (links.length === 0) {
return undefined
}
return {
title: rawGroup.title,
links,
}
})
.filter((group): group is GameCultSidebarGroup => group !== undefined)
return groups.length > 0 ? groups : undefined
}
function overviewCandidates(currentSlug: FullSlug, includeCurrent = true) {
if (currentSlug === "index") {
return includeCurrent ? (["index"] as FullSlug[]) : []
@ -431,39 +198,20 @@ function overviewCandidates(currentSlug: FullSlug, includeCurrent = true) {
return [...new Set(candidates)]
}
export function findSidebarOverviewNote(currentSlug: FullSlug, allFiles: QuartzPluginData[]) {
export function findSectionNote(currentSlug: FullSlug, allFiles: QuartzPluginData[]) {
const filesBySlug = fileBySlug(allFiles)
const includeCurrent = isOverviewSlug(currentSlug) && currentSlug !== "index"
for (const candidate of overviewCandidates(currentSlug, includeCurrent)) {
const match = filesBySlug.get(candidate)
if (match?.htmlAst) {
return resolveGameCultSourceFile(match, allFiles, "sidebarSource") ??
resolveGameCultSourceFile(match, allFiles, "contentSource") ??
match
return resolveGameCultSourceFile(match, allFiles) ?? match
}
}
return undefined
}
function extractOverviewData(file: QuartzPluginData): GameCultSidebarData | undefined {
if (!file.slug || !file.htmlAst) {
return undefined
}
const tagline = extractTopTagline(file.htmlAst)
const frontmatterGroups = extractFrontmatterOverviewGroups(file)
return {
title: file.frontmatter?.title ?? "GameCult",
slug: file.slug,
tagline: tagline?.text,
summary: extractOverviewSummary(file.htmlAst, tagline),
groups: frontmatterGroups ?? extractOverviewGroups(file.htmlAst, file.slug),
}
}
export function buildGameCultPageContext(
currentRoot: Root,
currentFile: QuartzPluginData,
@ -488,21 +236,9 @@ export function buildGameCultPageContext(
stripTopHeading(currentRoot)
}
const overviewNote = findSidebarOverviewNote(currentFile.slug, allFiles)
const sidebar = overviewNote
? overviewNote.slug === ("Blog/index" as FullSlug)
? buildAutoIndexSidebarData(overviewNote, allFiles, {
rootSlug: "Blog",
hideFrontmatterKey: "hideFromBlogIndex",
defaultAuthor: "GameCult",
sidebarTagline: "Recent notes, fiction, experiments, and other escaped materials.",
sidebarSummary: (count) => `${count} public posts, newest trouble first.`,
})
: extractOverviewData(overviewNote)
: undefined
const sectionNote = findSectionNote(currentFile.slug, allFiles)
return {
headerTagline: currentTaglineText ?? sidebar?.tagline,
sidebar,
headerTagline: currentTaglineText ?? extractTopTagline(sectionNote?.htmlAst)?.text,
}
}