mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
fix left sidebar expand toggle
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Project instructions for Claude Code.
|
||||
## Project context
|
||||
|
||||
<!-- Auto-updated by sync:discovery -->
|
||||
<!-- Site: markdown | Posts: 17 | Pages: 5 | Updated: 2025-12-30T23:27:44.146Z -->
|
||||
<!-- Site: markdown | Posts: 17 | Pages: 5 | Updated: 2025-12-31T01:30:04.562Z -->
|
||||
|
||||
Markdown sync framework. Write markdown in `content/`, run sync commands, content appears instantly via Convex real-time database. Built for developers and AI agents.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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<string> {
|
||||
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<string> {
|
||||
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(
|
||||
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]);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user