docs: update changelog page with right sidebar feature and fixes

Add v1.30.0, v1.30.1, and v1.30.2 entries to changelog-page.md covering:
- Right sidebar feature implementation
- TypeScript error fixes
- Right sidebar default behavior fix
This commit is contained in:
Wayne Sutton
2025-12-25 21:06:06 -08:00
parent 6b776733d5
commit d00f204fa7
30 changed files with 698 additions and 104 deletions

View File

@@ -0,0 +1,23 @@
import CopyPageDropdown from "./CopyPageDropdown";
interface RightSidebarProps {
title: string;
content: string;
url: string;
slug: string;
description?: string;
date?: string;
tags?: string[];
readTime?: string;
}
export default function RightSidebar(props: RightSidebarProps) {
return (
<aside className="post-sidebar-right">
<div className="right-sidebar-content">
<CopyPageDropdown {...props} />
</div>
</aside>
);
}

View File

@@ -80,6 +80,13 @@ export interface GitHubRepoConfig {
// default font family options: "serif" (New York), "sans" (system fonts), "monospace" (IBM Plex Mono)
export type FontFamily = "serif" | "sans" | "monospace";
// Right sidebar configuration
// Shows CopyPageDropdown in a right sidebar on posts/pages at 1135px+ viewport width
export interface RightSidebarConfig {
enabled: boolean; // Enable/disable the right sidebar globally
minWidth?: number; // Minimum viewport width to show sidebar (default: 1135)
}
// Site configuration interface
export interface SiteConfig {
// Basic site info
@@ -126,6 +133,9 @@ export interface SiteConfig {
// GitHub repository configuration for AI service links
gitHubRepo: GitHubRepoConfig;
// Right sidebar configuration
rightSidebar: RightSidebarConfig;
}
// Default site configuration
@@ -271,6 +281,14 @@ export const siteConfig: SiteConfig = {
branch: "main", // Default branch
contentPath: "public/raw", // Path to raw markdown files
},
// Right sidebar configuration
// Shows CopyPageDropdown in a right sidebar on posts/pages at 1135px+ viewport width
// When enabled, CopyPageDropdown moves from nav to right sidebar on wide screens
rightSidebar: {
enabled: true, // Set to false to disable right sidebar globally
minWidth: 1135, // Minimum viewport width in pixels to show sidebar
},
};
// Export the config as default for easy importing

View File

@@ -4,11 +4,13 @@ import { api } from "../../convex/_generated/api";
import BlogPost from "../components/BlogPost";
import CopyPageDropdown from "../components/CopyPageDropdown";
import PageSidebar from "../components/PageSidebar";
import RightSidebar from "../components/RightSidebar";
import { extractHeadings } from "../utils/extractHeadings";
import { useSidebar } from "../context/SidebarContext";
import { format, parseISO } from "date-fns";
import { ArrowLeft, Link as LinkIcon, Twitter, Rss, Tag } from "lucide-react";
import { useState, useEffect } from "react";
import siteConfig from "../config/siteConfig";
// Site configuration
const SITE_URL = "https://markdown.fast";
@@ -185,35 +187,40 @@ export default function Post() {
if (page) {
// Extract headings for sidebar TOC (only for pages with layout: "sidebar")
const headings = page.layout === "sidebar" ? extractHeadings(page.content) : [];
const hasSidebar = headings.length > 0;
const hasLeftSidebar = headings.length > 0;
// Check if right sidebar is enabled (only when explicitly set in frontmatter)
const hasRightSidebar = siteConfig.rightSidebar.enabled && page.rightSidebar === true;
const hasAnySidebar = hasLeftSidebar || hasRightSidebar;
return (
<div className={`post-page ${hasSidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasSidebar ? "post-nav-with-sidebar" : ""}`}>
<div className={`post-page ${hasAnySidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasAnySidebar ? "post-nav-with-sidebar" : ""}`}>
<button onClick={() => navigate("/")} className="back-button">
<ArrowLeft size={16} />
<span>Back</span>
</button>
{/* CopyPageDropdown in nav */}
<CopyPageDropdown
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
{!hasRightSidebar && (
<CopyPageDropdown
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
)}
</nav>
<div className={hasSidebar ? "post-content-with-sidebar" : ""}>
<div className={hasAnySidebar ? "post-content-with-sidebar" : ""}>
{/* Left sidebar - TOC */}
{hasSidebar && (
{hasLeftSidebar && (
<aside className="post-sidebar-wrapper post-sidebar-left">
<PageSidebar headings={headings} activeId={location.hash.slice(1)} />
</aside>
)}
{/* Main content */}
<article className={`post-article ${hasSidebar ? "post-article-with-sidebar" : ""}`}>
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
<header className="post-header">
<h1 className="post-title">{page.title}</h1>
{/* Author avatar and name for pages (optional) */}
@@ -237,6 +244,17 @@ export default function Post() {
<BlogPost content={page.content} />
</article>
{/* Right sidebar - CopyPageDropdown */}
{hasRightSidebar && (
<RightSidebar
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
)}
</div>
</div>
);
@@ -276,38 +294,43 @@ export default function Post() {
// Extract headings for sidebar TOC (only for posts with layout: "sidebar")
const headings = post?.layout === "sidebar" ? extractHeadings(post.content) : [];
const hasSidebar = headings.length > 0;
const hasLeftSidebar = headings.length > 0;
// Check if right sidebar is enabled (only when explicitly set in frontmatter)
const hasRightSidebar = siteConfig.rightSidebar.enabled && post.rightSidebar === true;
const hasAnySidebar = hasLeftSidebar || hasRightSidebar;
// Render blog post with full metadata
return (
<div className={`post-page ${hasSidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasSidebar ? "post-nav-with-sidebar" : ""}`}>
<div className={`post-page ${hasAnySidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasAnySidebar ? "post-nav-with-sidebar" : ""}`}>
<button onClick={() => navigate("/")} className="back-button">
<ArrowLeft size={16} />
<span>Back</span>
</button>
{/* Copy page dropdown for sharing with full metadata */}
<CopyPageDropdown
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
{!hasRightSidebar && (
<CopyPageDropdown
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
)}
</nav>
<div className={hasSidebar ? "post-content-with-sidebar" : ""}>
<div className={hasAnySidebar ? "post-content-with-sidebar" : ""}>
{/* Left sidebar - TOC */}
{hasSidebar && (
{hasLeftSidebar && (
<aside className="post-sidebar-wrapper post-sidebar-left">
<PageSidebar headings={headings} activeId={location.hash.slice(1)} />
</aside>
)}
<article className={`post-article ${hasSidebar ? "post-article-with-sidebar" : ""}`}>
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
<header className="post-header">
<h1 className="post-title">{post.title}</h1>
<div className="post-meta-header">
@@ -409,6 +432,20 @@ export default function Post() {
)}
</footer>
</article>
{/* Right sidebar - CopyPageDropdown */}
{hasRightSidebar && (
<RightSidebar
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
)}
</div>
</div>
);

View File

@@ -39,6 +39,7 @@ const POST_FIELDS = [
{ name: "authorName", required: false, example: '"Jane Doe"' },
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
{ name: "layout", required: false, example: '"sidebar"' },
{ name: "rightSidebar", required: false, example: "true" },
];
// Frontmatter field definitions for pages
@@ -55,6 +56,7 @@ const PAGE_FIELDS = [
{ name: "authorName", required: false, example: '"Jane Doe"' },
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
{ name: "layout", required: false, example: '"sidebar"' },
{ name: "rightSidebar", required: false, example: "true" },
];
// Generate frontmatter template based on content type

View File

@@ -871,7 +871,20 @@ body {
width: 100%;
}
/* Sidebar wrapper - docs-style with alt background and borders */
/* Three-column layout when right sidebar is enabled */
@media (min-width: 1135px) {
.post-content-with-sidebar:has(.post-sidebar-right) {
grid-template-columns: 240px 1fr 280px;
}
/* Adjust main content padding when right sidebar exists */
.post-content-with-sidebar:has(.post-sidebar-right)
.post-article-with-sidebar {
padding-right: 48px;
}
}
/* Left sidebar wrapper - docs-style with alt background and borders */
.post-sidebar-wrapper {
position: sticky;
top: 80px;
@@ -884,10 +897,11 @@ body {
background-color: var(--bg-sidebar);
margin-left: -24px;
padding-left: 24px;
padding-right: 24px;
padding-top: 24px;
padding-bottom: 24px;
margin-top: -24px;
border-radius: 8px;
border-radius: 6px;
/* Extend background to fill height */
min-height: calc(100vh - 80px);
/* Top border using CSS variable for theme consistency */
@@ -903,10 +917,50 @@ body {
}
/* Left sidebar - flush left with internal padding */
.post-sidebar-left {
/* All styles inherited from .post-sidebar-wrapper */
/* Right sidebar - complete styles based on left sidebar wrapper */
.post-sidebar-right {
position: sticky;
top: 80px;
align-self: flex-start;
max-height: calc(100vh - 100px);
overflow-y: auto;
/* Hide scrollbar while keeping scroll functionality */
-ms-overflow-style: none; /* IE */
scrollbar-width: none; /* Firefox */
background-color: var(--bg-sidebar);
margin-right: -24px;
padding-left: 24px;
padding-right: 24px;
padding-top: 24px;
padding-bottom: 24px;
margin-top: -24px;
border-radius: 6px;
/* Extend background to fill height */
min-height: calc(100vh - 80px);
/* Top border using CSS variable for theme consistency */
border-top: 1px solid var(--border-sidebar);
padding-right: 24px;
/* Left border using box-shadow for consistent 1px width regardless of scrollbar */
box-shadow: inset 1px 0 0 var(--border-sidebar);
}
/* Hide scrollbar for Chrome/Safari/Edge */
.post-sidebar-right::-webkit-scrollbar {
width: 0;
height: 0;
}
.right-sidebar-content {
position: sticky;
top: 0px;
}
/* Hide right sidebar below 1135px */
@media (max-width: 1134px) {
.post-sidebar-right {
display: none;
}
}
/* Content area - flexible width with left padding for gap from sidebar */
@@ -1040,11 +1094,16 @@ body {
gap: 24px;
}
/* Hide sidebar on mobile - it's now in the hamburger menu */
/* Hide sidebars on mobile - left sidebar is now in the hamburger menu */
.post-sidebar-wrapper {
display: none;
}
/* Ensure right sidebar is hidden on mobile */
.post-sidebar-right {
display: none;
}
/* Reset sidebar styles for mobile (when shown in hamburger) */
.post-sidebar-left {
position: static;