From db960ec1636bcbac6f22b776939687e500bc3d30 Mon Sep 17 00:00:00 2001 From: Wayne Sutton Date: Tue, 30 Dec 2025 21:11:21 -0800 Subject: [PATCH] fix left sidebar expand toggle --- AGENTS.md | 2 +- CLAUDE.md | 2 +- content/blog/how-to-publish.md | 1 + content/blog/using-images-in-posts.md | 1 + content/pages/docs.md | 4 +- public/llms.txt | 2 +- public/raw/docs.md | 4 +- src/components/PageSidebar.tsx | 65 +++++++++++++++++++++++---- src/config/siteConfig.ts | 2 +- src/styles/global.css | 6 ++- 10 files changed, 71 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6cfd05e..1d35764 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Your content is instantly available to browsers, LLMs, and AI agents.. Write mar - **Total Posts**: 17 - **Total Pages**: 5 - **Latest Post**: 2025-12-29 -- **Last Updated**: 2025-12-30T23:27:44.143Z +- **Last Updated**: 2025-12-31T01:30:04.561Z ## Tech stack diff --git a/CLAUDE.md b/CLAUDE.md index 3e70fcd..1ea9c6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ Project instructions for Claude Code. ## Project context - + Markdown sync framework. Write markdown in `content/`, run sync commands, content appears instantly via Convex real-time database. Built for developers and AI agents. diff --git a/content/blog/how-to-publish.md b/content/blog/how-to-publish.md index fa88771..64b34bb 100644 --- a/content/blog/how-to-publish.md +++ b/content/blog/how-to-publish.md @@ -7,6 +7,7 @@ published: true tags: ["tutorial", "markdown", "cursor", "IDE", "publishing"] readTime: "3 min read" featured: false +layout: "sidebar" featuredOrder: 3 authorName: "Markdown" blogFeatured: true diff --git a/content/blog/using-images-in-posts.md b/content/blog/using-images-in-posts.md index c03bd08..d6cfd54 100644 --- a/content/blog/using-images-in-posts.md +++ b/content/blog/using-images-in-posts.md @@ -5,6 +5,7 @@ date: "2025-12-14" slug: "using-images-in-posts" published: true featured: false +layout: "sidebar" featuredOrder: 4 tags: ["images", "tutorial", "markdown", "open-graph"] readTime: "4 min read" diff --git a/content/pages/docs.md b/content/pages/docs.md index 736fe30..a449463 100644 --- a/content/pages/docs.md +++ b/content/pages/docs.md @@ -122,7 +122,7 @@ Content here... | `layout` | No | Set to `"sidebar"` for docs-style layout with TOC | | `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) | | `showFooter` | No | Show footer on this post (overrides siteConfig default) | -| `footer` | No | Footer markdown content (overrides siteConfig.defaultContent) | +| `footer` | No | Per-post footer markdown (overrides `footer.md` and siteConfig.defaultContent) | | `showSocialFooter` | No | Show social footer on this post (overrides siteConfig default) | | `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. | | `blogFeatured` | No | Show as featured on blog page (first becomes hero, rest in 2-column row) | @@ -165,7 +165,7 @@ Content here... | `layout` | No | Set to `"sidebar"` for docs-style layout with TOC | | `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) | | `showFooter` | No | Show footer on this page (overrides siteConfig default) | -| `footer` | No | Footer markdown content (overrides siteConfig.defaultContent) | +| `footer` | No | Per-page footer markdown (overrides `footer.md` and siteConfig.defaultContent) | | `showSocialFooter` | No | Show social footer on this page (overrides siteConfig default) | | `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. | | `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) | diff --git a/public/llms.txt b/public/llms.txt index cbe00a6..abd7a8d 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,6 +1,6 @@ # llms.txt - Information for AI assistants and LLMs # Learn more: https://llmstxt.org/ -# Last updated: 2025-12-30T23:27:44.147Z +# Last updated: 2025-12-31T01:30:04.563Z > Your content is instantly available to browsers, LLMs, and AI agents. diff --git a/public/raw/docs.md b/public/raw/docs.md index 431e9fc..b03156c 100644 --- a/public/raw/docs.md +++ b/public/raw/docs.md @@ -119,7 +119,7 @@ Content here... | `layout` | No | Set to `"sidebar"` for docs-style layout with TOC | | `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) | | `showFooter` | No | Show footer on this post (overrides siteConfig default) | -| `footer` | No | Footer markdown content (overrides siteConfig.defaultContent) | +| `footer` | No | Per-post footer markdown (overrides `footer.md` and siteConfig.defaultContent) | | `showSocialFooter` | No | Show social footer on this post (overrides siteConfig default) | | `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. | | `blogFeatured` | No | Show as featured on blog page (first becomes hero, rest in 2-column row) | @@ -162,7 +162,7 @@ Content here... | `layout` | No | Set to `"sidebar"` for docs-style layout with TOC | | `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) | | `showFooter` | No | Show footer on this page (overrides siteConfig default) | -| `footer` | No | Footer markdown content (overrides siteConfig.defaultContent) | +| `footer` | No | Per-page footer markdown (overrides `footer.md` and siteConfig.defaultContent) | | `showSocialFooter` | No | Show social footer on this page (overrides siteConfig default) | | `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. | | `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) | diff --git a/src/components/PageSidebar.tsx b/src/components/PageSidebar.tsx index c6fc714..258b0dc 100644 --- a/src/components/PageSidebar.tsx +++ b/src/components/PageSidebar.tsx @@ -38,22 +38,54 @@ function buildHeadingTree(headings: Heading[]): HeadingNode[] { return tree; } +// Get ID of first root-level heading that has children (for default expanded state) +function getFirstExpandableId(headings: Heading[]): Set { + if (headings.length === 0) return new Set(); + + // Find minimum level (usually h2) + let minLevel = Infinity; + for (const h of headings) { + if (h.level < minLevel) minLevel = h.level; + } + + // Find first root heading that has a child (next heading with higher level) + for (let i = 0; i < headings.length; i++) { + if (headings[i].level === minLevel) { + // Check if this heading has children (next heading has higher level number) + if (i + 1 < headings.length && headings[i + 1].level > minLevel) { + return new Set([headings[i].id]); + } + } + } + + return new Set(); +} + // Load expanded state from localStorage function loadExpandedState(headings: Heading[]): Set { const stored = localStorage.getItem("page-sidebar-expanded-state"); if (stored) { try { - const storedIds = new Set(JSON.parse(stored)); - // Only return stored IDs that still exist in headings - return new Set( - headings.filter((h) => storedIds.has(h.id)).map((h) => h.id), - ); + const parsed = JSON.parse(stored) as string[]; + // If stored array has items, use it (filter to valid IDs) + if (parsed.length > 0) { + const storedIds = new Set(parsed); + const validIds = new Set( + headings.filter((h) => storedIds.has(h.id)).map((h) => h.id), + ); + // If valid IDs exist, use them; otherwise fall through to default + if (validIds.size > 0) { + return validIds; + } + } } catch { - // If parse fails, return empty (collapsed) + // If parse fails, use default } + // Clear stale/empty localStorage + localStorage.removeItem("page-sidebar-expanded-state"); } - // Default: all headings collapsed - return new Set(); + // Default: expand only the first expandable heading + return getFirstExpandableId(headings); } // Save expanded state to localStorage @@ -159,6 +191,23 @@ export default function PageSidebar({ headings, activeId }: PageSidebarProps) { // Build tree structure from headings const headingTree = useMemo(() => buildHeadingTree(headings), [headings]); + // Reset expanded state when headings change (new page) to expand root items + useEffect(() => { + if (headings.length > 0) { + setExpanded((prev) => { + const currentIds = new Set(headings.map((h) => h.id)); + const hasMatchingIds = Array.from(prev).some((id) => + currentIds.has(id), + ); + // If no current expanded IDs match new headings, reset to first expanded + if (!hasMatchingIds) { + return getFirstExpandableId(headings); + } + return prev; + }); + } + }, [headings]); + // Get all heading IDs for scroll tracking const allHeadingIds = useMemo(() => headings.map((h) => h.id), [headings]); diff --git a/src/config/siteConfig.ts b/src/config/siteConfig.ts index 636d3a2..e823d28 100644 --- a/src/config/siteConfig.ts +++ b/src/config/siteConfig.ts @@ -388,7 +388,7 @@ export const siteConfig: SiteConfig = { }, ], position: "above-footer", - speed: 30, + speed: 20, title: "Built with", scrolling: false, // Set to false for static grid showing first maxItems logos maxItems: 4, // Number of logos to show when scrolling is false diff --git a/src/styles/global.css b/src/styles/global.css index 2a95512..f5fda5f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1742,7 +1742,9 @@ body { align-items: center; justify-content: center; cursor: pointer; - transition: background-color 0.2s ease, border-color 0.2s ease; + transition: + background-color 0.2s ease, + border-color 0.2s ease; z-index: 10001; } @@ -4534,7 +4536,7 @@ body { } .logo-marquee-image { - height: 24px; + height: 50px; max-width: 80px; } }