mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
Add image support to footer component with size control via HTML attributes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,3 +36,4 @@ fork-config.json
|
||||
|
||||
# PRD files
|
||||
prds/metadataforsubs.md
|
||||
prds/addchattoapp.md
|
||||
|
||||
27
TASK.md
27
TASK.md
@@ -8,10 +8,35 @@
|
||||
|
||||
## Current Status
|
||||
|
||||
v1.30.2 ready. Right sidebar now opt-in only via frontmatter.
|
||||
v1.31.1 ready. Footer component now supports images with size control via HTML attributes.
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] Image support in footer component with size control
|
||||
- [x] Footer sanitize schema updated to allow width, height, style, class attributes on images
|
||||
- [x] Footer image component handler updated to pass through size attributes
|
||||
- [x] CSS styles added for footer images (.site-footer-image-wrapper, .site-footer-image, .site-footer-image-caption)
|
||||
- [x] Images support lazy loading and optional captions from alt text
|
||||
- [x] Security verified: rehypeSanitize sanitizes style attributes to remove dangerous CSS
|
||||
- [x] Updated files.md, changelog.md with image support documentation
|
||||
|
||||
- [x] Customizable footer component with markdown support
|
||||
- [x] Footer component created (src/components/Footer.tsx) with ReactMarkdown rendering
|
||||
- [x] Footer configuration added to siteConfig.ts (FooterConfig interface with defaultContent)
|
||||
- [x] Footer content can be set in frontmatter footer field (markdown) or siteConfig.defaultContent
|
||||
- [x] Footer can be enabled/disabled globally and per-page type
|
||||
- [x] showFooter and footer frontmatter fields added for posts and pages
|
||||
- [x] Footer renders inside article tag at bottom for posts/pages
|
||||
- [x] Footer maintains current position on homepage
|
||||
- [x] Updated Home.tsx to use Footer component with defaultContent
|
||||
- [x] Updated Post.tsx to render Footer inside article based on showFooter
|
||||
- [x] Added CSS styles for site-footer (.site-footer, .site-footer-content, .site-footer-text, .site-footer-link)
|
||||
- [x] Updated schema.ts, posts.ts, pages.ts with showFooter and footer fields
|
||||
- [x] Updated sync-posts.ts to parse showFooter and footer frontmatter
|
||||
- [x] Updated Write.tsx to include showFooter and footer in frontmatter reference
|
||||
- [x] Sidebars flush to bottom when footer is enabled (min-height ensures proper extension)
|
||||
- [x] Updated files.md, changelog.md with footer feature documentation
|
||||
|
||||
- [x] Fixed right sidebar default behavior: now requires explicit `rightSidebar: true` in frontmatter
|
||||
- [x] Pages/posts without rightSidebar frontmatter render normally with CopyPageDropdown in nav
|
||||
- [x] Fixed TypeScript errors: Added rightSidebar to syncPosts and syncPostsPublic args validators
|
||||
|
||||
38
changelog.md
38
changelog.md
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [1.31.1] - 2025-12-25
|
||||
|
||||
### Added
|
||||
|
||||
- Image support in footer component with size control
|
||||
- Footer markdown now supports images using standard markdown syntax or HTML
|
||||
- Images can be sized using `width`, `height`, `style`, or `class` HTML attributes
|
||||
- Image attributes are sanitized by rehypeSanitize for security (removes dangerous CSS)
|
||||
- Footer images support lazy loading and optional captions from alt text
|
||||
- CSS styles added for footer images (`.site-footer-image-wrapper`, `.site-footer-image`, `.site-footer-image-caption`)
|
||||
|
||||
### Changed
|
||||
|
||||
- Footer sanitize schema updated to allow `width`, `height`, `style`, and `class` attributes on images
|
||||
- Footer image component handler updated to pass through size attributes from HTML
|
||||
|
||||
## [1.31.0] - 2025-12-25
|
||||
|
||||
### Added
|
||||
|
||||
- Customizable footer component with markdown support
|
||||
- New `Footer` component (`src/components/Footer.tsx`) that renders markdown content
|
||||
- Footer content can be set in frontmatter `footer` field (markdown) or use `siteConfig.footer.defaultContent`
|
||||
- Footer can be enabled/disabled globally via `siteConfig.footer.enabled`
|
||||
- Footer visibility controlled per-page type via `siteConfig.footer.showOnHomepage`, `showOnPosts`, `showOnPages`
|
||||
- New `showFooter` frontmatter field for posts and pages to override siteConfig defaults
|
||||
- New `footer` frontmatter field for posts and pages to provide custom markdown content
|
||||
- Footer renders inside article at bottom for posts/pages, maintains current position on homepage
|
||||
- Footer supports markdown formatting (links, paragraphs, line breaks)
|
||||
- Sidebars flush to bottom when footer is enabled (using min-height)
|
||||
|
||||
### Changed
|
||||
|
||||
- Homepage footer section now uses the new `Footer` component instead of hardcoded HTML
|
||||
- Post and page views now render footer inside article tag (before closing `</article>`)
|
||||
- Footer component simplified to accept markdown content instead of structured link arrays
|
||||
- Footer configuration in `siteConfig.ts` now uses `defaultContent` (markdown string) instead of `builtWith`/`createdBy` objects
|
||||
|
||||
## [1.30.2] - 2025-12-25
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -9,6 +9,40 @@ layout: "sidebar"
|
||||
All notable changes to this project.
|
||||

|
||||
|
||||
## v1.31.1
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Image support in footer component**
|
||||
|
||||
- Footer markdown now supports images using standard markdown syntax or HTML
|
||||
- Images can be sized using `width`, `height`, `style`, or `class` HTML attributes
|
||||
- Image attributes are sanitized by rehypeSanitize for security (removes dangerous CSS)
|
||||
- Footer images support lazy loading and optional captions from alt text
|
||||
- CSS styles added for footer images
|
||||
|
||||
Updated files: `src/components/Footer.tsx`, `src/styles/global.css`
|
||||
|
||||
## v1.31.0
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Customizable footer component with markdown support**
|
||||
|
||||
- New `Footer` component that renders markdown content
|
||||
- Footer content can be set in frontmatter `footer` field (markdown) or use `siteConfig.footer.defaultContent`
|
||||
- Footer can be enabled/disabled globally via `siteConfig.footer.enabled`
|
||||
- Footer visibility controlled per-page type via `siteConfig.footer.showOnHomepage`, `showOnPosts`, `showOnPages`
|
||||
- New `showFooter` frontmatter field for posts and pages to override siteConfig defaults
|
||||
- New `footer` frontmatter field for posts and pages to provide custom markdown content
|
||||
- Footer renders inside article at bottom for posts/pages, maintains current position on homepage
|
||||
- Footer supports markdown formatting (links, paragraphs, line breaks)
|
||||
- Sidebars flush to bottom when footer is enabled
|
||||
|
||||
Updated files: `src/components/Footer.tsx`, `src/pages/Home.tsx`, `src/pages/Post.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `convex/schema.ts`, `convex/posts.ts`, `convex/pages.ts`, `scripts/sync-posts.ts`, `src/pages/Write.tsx`
|
||||
|
||||
Documentation updated: `files.md`, `changelog.md`
|
||||
|
||||
## v1.30.2
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
@@ -5,6 +5,7 @@ published: true
|
||||
order: 0
|
||||
layout: "sidebar"
|
||||
rightSidebar: true
|
||||
footer: true
|
||||
---
|
||||
|
||||
Reference documentation for setting up, customizing, and deploying this markdown framework.
|
||||
|
||||
@@ -20,6 +20,8 @@ export const getAllPages = query({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
@@ -57,6 +59,7 @@ export const getAllPages = query({
|
||||
authorImage: page.authorImage,
|
||||
layout: page.layout,
|
||||
rightSidebar: page.rightSidebar,
|
||||
showFooter: page.showFooter,
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -122,6 +125,8 @@ export const getPageBySlug = query({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
@@ -151,6 +156,8 @@ export const getPageBySlug = query({
|
||||
authorImage: page.authorImage,
|
||||
layout: page.layout,
|
||||
rightSidebar: page.rightSidebar,
|
||||
showFooter: page.showFooter,
|
||||
footer: page.footer,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -174,6 +181,8 @@ export const syncPagesPublic = mutation({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -214,6 +223,8 @@ export const syncPagesPublic = mutation({
|
||||
authorImage: page.authorImage,
|
||||
layout: page.layout,
|
||||
rightSidebar: page.rightSidebar,
|
||||
showFooter: page.showFooter,
|
||||
footer: page.footer,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
|
||||
@@ -23,6 +23,8 @@ export const getAllPosts = query({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
@@ -55,6 +57,7 @@ export const getAllPosts = query({
|
||||
authorImage: post.authorImage,
|
||||
layout: post.layout,
|
||||
rightSidebar: post.rightSidebar,
|
||||
showFooter: post.showFooter,
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -125,6 +128,8 @@ export const getPostBySlug = query({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
@@ -157,6 +162,8 @@ export const getPostBySlug = query({
|
||||
authorImage: post.authorImage,
|
||||
layout: post.layout,
|
||||
rightSidebar: post.rightSidebar,
|
||||
showFooter: post.showFooter,
|
||||
footer: post.footer,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -182,6 +189,8 @@ export const syncPosts = internalMutation({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -224,6 +233,8 @@ export const syncPosts = internalMutation({
|
||||
authorImage: post.authorImage,
|
||||
layout: post.layout,
|
||||
rightSidebar: post.rightSidebar,
|
||||
showFooter: post.showFooter,
|
||||
footer: post.footer,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
@@ -270,6 +281,8 @@ export const syncPostsPublic = mutation({
|
||||
authorImage: v.optional(v.string()),
|
||||
layout: v.optional(v.string()),
|
||||
rightSidebar: v.optional(v.boolean()),
|
||||
showFooter: v.optional(v.boolean()),
|
||||
footer: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -312,6 +325,8 @@ export const syncPostsPublic = mutation({
|
||||
authorImage: post.authorImage,
|
||||
layout: post.layout,
|
||||
rightSidebar: post.rightSidebar,
|
||||
showFooter: post.showFooter,
|
||||
footer: post.footer,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
|
||||
@@ -20,6 +20,8 @@ export default defineSchema({
|
||||
authorImage: v.optional(v.string()), // Author avatar image URL (round)
|
||||
layout: v.optional(v.string()), // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar: v.optional(v.boolean()), // Enable right sidebar with CopyPageDropdown
|
||||
showFooter: v.optional(v.boolean()), // Show footer on this post (overrides siteConfig default)
|
||||
footer: v.optional(v.string()), // Footer markdown content (overrides siteConfig defaultContent)
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
@@ -51,6 +53,8 @@ export default defineSchema({
|
||||
authorImage: v.optional(v.string()), // Author avatar image URL (round)
|
||||
layout: v.optional(v.string()), // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar: v.optional(v.boolean()), // Enable right sidebar with CopyPageDropdown
|
||||
showFooter: v.optional(v.boolean()), // Show footer on this page (overrides siteConfig default)
|
||||
footer: v.optional(v.string()), // Footer markdown content (overrides siteConfig defaultContent)
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
|
||||
10
files.md
10
files.md
@@ -33,7 +33,7 @@ A brief description of each file in the codebase.
|
||||
|
||||
| File | Description |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration) |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration, footer configuration) |
|
||||
|
||||
### Pages (`src/pages/`)
|
||||
|
||||
@@ -55,6 +55,7 @@ A brief description of each file in the codebase.
|
||||
| `PostList.tsx` | Year-grouped blog post list or card grid (supports list/cards view modes) |
|
||||
| `BlogPost.tsx` | Markdown renderer with syntax highlighting, collapsible sections (details/summary), and text wrapping for plain text code blocks |
|
||||
| `CopyPageDropdown.tsx` | Share dropdown with Copy page (markdown to clipboard), View as Markdown (opens raw .md file), Download as SKILL.md (Anthropic Agent Skills format), and Open in AI links (ChatGPT, Claude, Perplexity) using GitHub raw URLs with universal prompt |
|
||||
| `Footer.tsx` | Footer component that renders markdown content from frontmatter footer field or siteConfig.defaultContent. Can be enabled/disabled globally and per-page via frontmatter showFooter field. Renders inside article at bottom for posts/pages, and in current position on homepage. Supports images with size control via HTML attributes (width, height, style, class) |
|
||||
| `SearchModal.tsx` | Full text search modal with keyboard navigation |
|
||||
| `FeaturedCards.tsx` | Card grid for featured posts/pages with excerpts |
|
||||
| `LogoMarquee.tsx` | Scrolling logo gallery with clickable links |
|
||||
@@ -89,7 +90,7 @@ A brief description of each file in the codebase.
|
||||
|
||||
| File | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------ |
|
||||
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout. Left sidebar (`.post-sidebar-wrapper`) and right sidebar (`.post-sidebar-right`) have separate, independent styles |
|
||||
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout. Left sidebar (`.post-sidebar-wrapper`) and right sidebar (`.post-sidebar-right`) have separate, independent styles. Footer image styles (`.site-footer-image-wrapper`, `.site-footer-image`, `.site-footer-image-caption`) for responsive image display |
|
||||
|
||||
## Convex Backend (`convex/`)
|
||||
|
||||
@@ -141,6 +142,9 @@ Markdown files with frontmatter for blog posts. Each file becomes a blog post.
|
||||
| `featuredOrder` | Order in featured section (optional) |
|
||||
| `authorName` | Author display name (optional) |
|
||||
| `authorImage` | Round author avatar image URL (optional) |
|
||||
| `rightSidebar` | Enable right sidebar with CopyPageDropdown (optional) |
|
||||
| `showFooter` | Show footer on this post (optional, overrides siteConfig default) |
|
||||
| `footer` | Footer markdown content (optional, overrides siteConfig.defaultContent) |
|
||||
|
||||
## Static Pages (`content/pages/`)
|
||||
|
||||
@@ -159,6 +163,8 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
|
||||
| `authorName` | Author display name (optional) |
|
||||
| `authorImage` | Round author avatar image URL (optional) |
|
||||
| `rightSidebar` | Enable right sidebar with CopyPageDropdown (optional) |
|
||||
| `showFooter` | Show footer on this page (optional, overrides siteConfig default) |
|
||||
| `footer` | Footer markdown content (optional, overrides siteConfig.defaultContent) |
|
||||
|
||||
## Scripts (`scripts/`)
|
||||
|
||||
|
||||
14
public/images/logos/agentmail.svg
Normal file
14
public/images/logos/agentmail.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="633" height="636" viewBox="0 0 633 636" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M130 174C132.915 175.055 135.767 176.237 138.625 177.438C192.077 199.275 249.945 216.533 308 218C309.106 218.038 310.212 218.076 311.352 218.115C328.692 218.66 345.586 217.824 362.812 215.75C364.106 215.596 364.106 215.596 365.426 215.44C378.086 213.908 390.53 211.655 403 209C404.085 208.772 404.085 208.772 405.192 208.539C425.371 204.288 445.006 197.529 464.438 190.688C465.391 190.353 465.391 190.353 466.365 190.012C473.79 187.398 481.085 184.588 488.309 181.457C489.674 180.875 491.04 180.292 492.406 179.71C494.998 178.604 497.562 177.452 500.119 176.268C508.516 172.656 508.516 172.656 512.493 173.578C514.877 174.858 516.878 176.323 519 178C520.618 179.053 522.244 180.096 523.875 181.129C525.586 182.269 527.294 183.414 529 184.563C529.916 185.177 530.831 185.791 531.775 186.424C548.192 197.477 564.176 209.116 580 221C578.699 224.903 576.389 226.086 573.062 228.188C572.438 228.583 571.813 228.978 571.169 229.385C526.511 256.786 466.313 267.401 415.335 276.396C410.643 277.246 405.98 278.215 401.312 279.188C363.213 286.941 322.338 291.164 283.642 286.325C280.688 285.962 277.731 285.632 274.773 285.305C264.267 284.122 253.882 282.756 243.516 280.676C240.202 280.039 236.908 279.557 233.562 279.125C223.703 277.771 213.98 275.671 204.237 273.665C201.183 273.038 198.127 272.422 195.07 271.809C86.5249 249.861 86.5249 249.861 54 223C55.1115 219.666 55.6785 219.234 58.4375 217.285C59.1586 216.77 59.8796 216.255 60.6226 215.725C61.4071 215.176 62.1917 214.628 63 214.063C63.8116 213.487 64.6233 212.911 65.4595 212.318C67.1482 211.122 68.8391 209.929 70.5322 208.739C72.7883 207.149 75.0331 205.545 77.2734 203.934C86.4222 197.374 95.7589 191.145 105.238 185.074C108.25 183.144 111.224 181.176 114.168 179.145C115.116 178.501 115.116 178.501 116.083 177.845C117.748 176.712 119.402 175.564 121.055 174.414C124.742 172.644 126.107 172.839 130 174Z" fill="#666666"/>
|
||||
<path d="M195 41C202.979 45.3376 210.935 49.7163 218.875 54.125C219.461 54.4501 220.046 54.7753 220.65 55.1103C230.085 60.352 239.474 65.6713 248.836 71.043C255.287 74.7431 261.762 78.4017 268.241 82.0527C273.267 84.8852 278.283 87.7322 283.285 90.6055C284.315 91.1947 285.345 91.7838 286.407 92.3909C288.37 93.5142 290.331 94.6421 292.289 95.7751C304.515 102.753 315.189 105.128 329.215 102.367C338.945 99.0499 347.766 93.0512 356.547 87.8164C362.801 84.0941 369.136 80.5312 375.5 77C383.723 72.4254 391.885 67.7635 400 63C408.569 57.9792 417.167 53.0138 425.812 48.125C426.887 47.5114 427.961 46.8978 429.068 46.2656C430.56 45.4264 430.56 45.4264 432.082 44.5703C432.961 44.0729 433.84 43.5755 434.745 43.063C437 42 437 42 440 42C445.464 57.1854 450.857 72.39 456.085 87.6584C458.449 94.5475 460.851 101.421 463.297 108.281C463.574 109.061 463.852 109.84 464.137 110.643C465.5 114.471 466.867 118.298 468.242 122.122C468.737 123.511 469.232 124.9 469.727 126.289C470.166 127.513 470.605 128.737 471.057 129.999C472 133 472 133 472 136C440.152 148.715 407.791 157.445 373.882 162.565C371.686 162.896 369.493 163.244 367.301 163.598C361.124 164.559 355.001 165.206 348.758 165.539C347.98 165.581 347.202 165.622 346.4 165.665C337.457 166.115 328.517 166.286 319.562 166.312C318.685 166.315 317.808 166.318 316.905 166.32C298.723 166.326 280.969 164.668 263 162C261.319 161.752 261.319 161.752 259.604 161.499C239.767 158.482 220.505 153.648 201.355 147.719C198.386 146.813 195.403 145.958 192.419 145.104C183.141 142.443 174.067 139.302 165 136C165.602 131.854 166.533 128.066 167.891 124.105C168.461 122.422 168.461 122.422 169.043 120.705C169.441 119.544 169.84 118.383 170.25 117.188C172.779 109.802 175.21 102.409 177.379 94.9102C180.003 85.8453 182.892 76.8726 185.822 67.9023C186.7 65.212 187.574 62.5201 188.447 59.8281C189.01 58.0989 189.573 56.3697 190.137 54.6406C190.524 53.4469 190.524 53.4469 190.92 52.2291C192.163 48.4303 193.454 44.6896 195 41Z" fill="#666666"/>
|
||||
<path d="M191.999 300C195.673 300.27 199.335 300.618 202.999 301C203.944 301.088 204.889 301.175 205.862 301.266C212.098 302.077 215.627 303.815 220.374 307.867C234.08 319.003 250.015 327.197 268.057 325.343C274.297 324.241 279.443 320.976 283.311 316.062C286.677 313.48 287.524 313.922 291.643 314.395C298.325 314.986 304.88 315 311.585 314.852C313.69 314.816 315.796 314.781 317.901 314.746C321.154 314.69 324.405 314.624 327.656 314.527C330.841 314.436 334.024 314.39 337.21 314.352C338.657 314.293 338.657 314.293 340.133 314.233C345.503 314.211 348.417 314.844 352.512 318.443C353.676 319.627 354.838 320.812 355.999 322C363.362 326.445 371 327.025 379.354 325.598C393.377 322.004 405.602 313.575 416.999 305C420.477 302.681 423.157 302.182 427.249 301.375C428.533 301.115 429.817 300.854 431.139 300.586C434.83 300.026 438.273 299.894 441.999 300C441.205 309.385 439.75 318.644 438.249 327.937C438.05 329.172 438.05 329.172 437.846 330.432C436.323 339.794 434.789 349.131 432.374 358.312C432.192 359.008 432.011 359.703 431.824 360.42C429.599 368.256 427.006 373.911 420.249 378.687C419.084 379.534 417.922 380.383 416.76 381.234C416.149 381.678 415.537 382.123 414.906 382.58C411.536 385.089 408.307 387.77 405.061 390.437C398.341 395.93 391.549 401.32 384.708 406.662C382.993 408.004 381.284 409.355 379.577 410.707C332.628 447.872 332.628 447.872 309.999 446C292.977 442.531 277.818 428.302 264.55 417.977C262.159 416.124 259.753 414.291 257.339 412.469C250.286 407.141 243.373 401.723 236.673 395.956C232.997 392.804 229.227 389.808 225.374 386.875C224.839 386.465 224.303 386.055 223.752 385.633C221.136 383.629 218.513 381.635 215.878 379.656C214.907 378.903 213.936 378.151 212.936 377.375C212.079 376.723 211.222 376.07 210.339 375.398C205.249 370.181 203.229 363.269 201.487 356.336C201.278 355.52 201.068 354.705 200.853 353.865C198.67 345.098 197.097 336.235 195.631 327.324C195.178 324.576 194.712 321.831 194.243 319.086C193.946 317.32 193.65 315.555 193.354 313.789C193.217 312.978 193.079 312.166 192.938 311.33C192.314 307.511 191.83 303.886 191.999 300Z" fill="#666666"/>
|
||||
<path d="M30 322C35.0226 325.767 39.4838 329.21 44 333.438C45.0385 334.402 46.0776 335.365 47.1172 336.328C48.0685 337.21 49.0198 338.092 50 339C55.1825 343.685 60.5488 348.05 66.0938 352.301C66.7228 352.862 67.3519 353.422 68 354C68 354.66 68 355.32 68 356C68.5517 356.233 69.1034 356.467 69.6719 356.707C72.6126 358.34 74.7908 360.393 77.25 362.688C78.2391 363.602 79.2287 364.516 80.2188 365.43C81.5955 366.702 81.5955 366.702 83 368C85.6435 370.36 88.3224 372.679 91 375C92.241 376.083 93.4819 377.167 94.7227 378.25C96.0858 379.438 97.4491 380.625 98.8125 381.812C99.4715 382.388 100.131 382.964 100.81 383.558C101.436 384.102 102.062 384.646 102.707 385.207C103.269 385.697 103.831 386.186 104.409 386.691C106.178 388.191 106.178 388.191 109 390C109 390.66 109 391.32 109 392C109.544 392.226 110.088 392.451 110.648 392.684C113.736 394.412 116.045 396.646 118.625 399.062C122.042 402.415 122.042 402.415 126 405C126 405.66 126 406.32 126 407C126.545 407.226 127.091 407.451 127.652 407.684C130.698 409.391 133.053 411.671 135.562 414.062C141.795 419.895 148.325 425.302 155.02 430.598C156.38 431.691 157.697 432.838 159 434C159 434.66 159 435.32 159 436C159.535 436.218 160.07 436.436 160.621 436.66C163.844 438.476 166.386 440.918 169.062 443.438C169.592 443.928 170.121 444.418 170.666 444.924C173.156 447.239 175.621 449.571 178 452C174.61 455.486 171.189 458.892 167.5 462.062C163.395 465.598 159.45 469.293 155.5 473C147.656 480.358 147.656 480.358 144 483.5C139.417 487.442 135.043 491.609 130.633 495.742C126.303 499.796 121.925 503.791 117.504 507.746C110.301 514.23 103.223 520.852 96.1492 527.476C91.2369 532.075 86.3224 536.644 81.2188 541.031C77.9204 543.958 74.7162 546.984 71.5 550C63.6494 557.363 63.6494 557.363 60 560.5C57.3601 562.785 54.7866 565.12 52.2578 567.527C51.5798 568.168 50.9017 568.809 50.2031 569.469C48.8319 570.768 47.4646 572.071 46.1016 573.379C45.158 574.272 45.158 574.272 44.1953 575.184C43.6283 575.726 43.0613 576.268 42.4771 576.826C41 578 41 578 39 578C39 578.66 39 579.32 39 580C37.2852 581.727 37.2852 581.727 35.0625 583.625C33.9662 584.572 33.9662 584.572 32.8477 585.539C31 587 31 587 30 587C30 499.55 30 412.1 30 322Z" fill="#666666"/>
|
||||
<path d="M604 322C604 409.45 604 496.9 604 587C595.249 579.5 586.707 571.901 578.355 563.961C574.294 560.114 570.168 556.371 565.942 552.706C562.769 549.918 559.7 547.021 556.625 544.125C552.451 540.21 548.269 536.312 544 532.5C539.731 528.688 535.55 524.789 531.375 520.875C527.054 516.825 522.71 512.83 518.219 508.969C514.28 505.474 510.461 501.848 506.629 498.236C503.478 495.275 500.297 492.366 497.031 489.531C492.227 485.356 487.62 480.982 483 476.605C477.51 471.427 471.981 466.298 466 461.688C465.34 461.131 464.68 460.574 464 460C464 459.34 464 458.68 464 458C463.134 457.66 463.134 457.66 462.25 457.312C459.438 455.672 457.97 453.562 456 451C460.994 446.418 466.027 441.932 471.312 437.688C476.127 433.796 480.651 429.669 485.166 425.44C488.491 422.343 491.887 419.404 495.438 416.562C500.459 412.535 505.126 408.207 509.808 403.797C514.101 399.776 518.563 396.05 523.156 392.375C526.556 389.535 529.776 386.536 533 383.5C537.91 378.877 542.945 374.534 548.211 370.316C552.637 366.64 556.808 362.696 561.016 358.773C565.609 354.491 570.354 350.396 575.141 346.332C581.805 340.663 588.363 334.917 594.737 328.921C602.123 322 602.123 322 604 322Z" fill="#666666"/>
|
||||
<path d="M465 499C468.203 500.226 470.376 502.093 472.828 504.449C473.546 505.133 474.264 505.817 475.004 506.521C476.116 507.594 476.116 507.594 477.25 508.688C483.543 514.718 489.845 520.714 496.688 526.125C499.315 528.255 501.608 530.608 504 533C506.128 534.834 508.268 536.65 510.426 538.449C510.945 538.961 511.465 539.473 512 540C512 540.66 512 541.32 512 542C512.584 542.262 513.168 542.524 513.77 542.793C516.127 544.069 517.757 545.456 519.688 547.309C520.38 547.968 521.073 548.627 521.787 549.306C522.517 550.01 523.248 550.713 524 551.438C525.532 552.896 527.065 554.355 528.598 555.812C529.348 556.528 530.098 557.243 530.872 557.98C535.518 562.389 540.259 566.694 545 571C545.941 571.859 546.882 572.717 547.852 573.602C548.746 574.414 549.641 575.226 550.562 576.062C551.348 576.775 552.133 577.488 552.941 578.223C555.078 580.141 555.078 580.141 558 582C558 582.66 558 583.32 558 584C558.859 584.365 558.859 584.365 559.734 584.738C562.404 586.225 564.138 587.981 566.25 590.188C569.805 593.836 573.465 597.225 577.371 600.492C579 602 579 602 581 605C505.76 605 430.52 605 353 605C359.75 597.125 359.75 597.125 362.41 594.676C363.001 594.128 363.592 593.58 364.201 593.016C365.122 592.173 365.122 592.173 366.062 591.312C366.712 590.712 367.362 590.111 368.031 589.492C370.017 587.657 372.008 585.828 374 584C376.313 581.876 378.626 579.751 380.938 577.625C381.47 577.137 382.003 576.648 382.552 576.145C386.336 572.66 389.971 569.059 393.512 565.328C395 564 395 564 397 564C397 563.34 397 562.68 397 562C398.469 560.52 398.469 560.52 400.5 558.812C401.228 558.192 401.957 557.572 402.707 556.934C403.464 556.296 404.22 555.657 405 555C406.399 553.795 407.795 552.587 409.188 551.375C409.8 550.846 410.412 550.318 411.043 549.773C414.363 546.764 417.467 543.54 420.613 540.352C425.326 535.591 430.074 530.961 435.172 526.609C437.029 524.974 438.766 523.265 440.5 521.5C442.83 519.128 445.221 516.909 447.75 514.75C451.821 511.254 455.576 507.483 459.319 503.644C462.796 500.102 462.796 500.102 465 499Z" fill="#666666"/>
|
||||
<path d="M169.246 499.051C171 500 171 500 172 503C172.572 503.285 173.145 503.57 173.734 503.863C176.86 505.432 178.826 507.677 181.25 510.188C185.059 514.088 188.958 517.785 193.105 521.324C195.871 523.77 198.422 526.414 201.012 529.043C202.886 530.887 204.813 532.606 206.812 534.313C210.202 537.223 213.364 540.322 216.5 543.5C221.532 548.593 226.794 553.302 232.27 557.91C233.006 558.538 233.742 559.166 234.5 559.813C235.505 560.658 235.505 560.658 236.531 561.52C238 563 238 563 238 565C238.531 565.202 239.062 565.405 239.609 565.613C243.041 567.604 245.511 570.343 248.25 573.188C252.312 577.382 256.473 581.343 260.894 585.159C264.66 588.452 268.294 591.885 271.938 595.313C272.704 596.027 273.47 596.742 274.26 597.479C274.987 598.162 275.715 598.846 276.465 599.551C277.121 600.166 277.777 600.781 278.453 601.415C280 603 280 603 281 605C205.76 605 130.52 605 53 605C56.8776 600.476 60.278 596.654 64.625 592.75C65.6695 591.809 66.7138 590.868 67.7578 589.926C68.2892 589.448 68.8206 588.971 69.3682 588.479C71.0286 586.974 72.6689 585.45 74.3047 583.918C74.8401 583.419 75.3756 582.92 75.9272 582.405C77.422 581.01 78.9142 579.613 80.4062 578.215C82.5898 576.35 84.3738 575.096 87 574C87 573.34 87 572.68 87 572C88.3281 570.66 88.3281 570.66 90.25 569.063C93.7958 566.046 97.2598 562.963 100.688 559.813C101.437 559.126 101.437 559.126 102.202 558.425C105.015 555.843 107.81 553.243 110.59 550.625C111.18 550.072 111.77 549.52 112.378 548.95C113.526 547.874 114.671 546.793 115.811 545.708C119.776 542 119.776 542 122 542C122.33 541.01 122.66 540.02 123 539C124.426 537.563 124.426 537.563 126.312 536C129.838 533.008 133.286 529.945 136.688 526.813C137.194 526.347 137.7 525.882 138.222 525.403C141.459 522.425 144.69 519.441 147.919 516.455C149.009 515.448 150.101 514.443 151.194 513.44C153.868 510.984 156.517 508.51 159.125 505.984C159.621 505.513 160.116 505.041 160.627 504.555C161.979 503.267 163.32 501.968 164.66 500.668C167 499 167 499 169.246 499.051Z" fill="#666666"/>
|
||||
<path d="M563.999 271C566.405 272.378 568.569 273.768 570.811 275.375C572.13 276.289 573.45 277.2 574.772 278.109C575.457 278.583 576.143 279.057 576.849 279.545C580.482 282.004 584.207 284.314 587.936 286.625C588.64 287.062 589.345 287.5 590.07 287.951C591.712 288.969 593.355 289.985 594.999 291C593.765 294.7 592.36 295.547 589.311 297.937C584.841 301.505 580.454 305.139 576.124 308.875C574.93 309.902 573.736 310.93 572.542 311.957C571.657 312.718 571.657 312.718 570.754 313.495C566.393 317.235 561.997 320.935 557.607 324.641C555.446 326.467 553.285 328.295 551.125 330.123C550.043 331.039 548.96 331.955 547.878 332.871C545.142 335.185 542.409 337.5 539.678 339.82C534.673 344.07 529.657 348.297 524.561 352.437C519.697 356.395 514.94 360.465 510.19 364.558C506.666 367.589 503.108 370.57 499.499 373.5C495.318 376.896 491.207 380.364 487.124 383.875C486.536 384.38 485.949 384.885 485.344 385.405C484.154 386.427 482.965 387.45 481.776 388.473C478.897 390.946 476.014 393.414 473.124 395.875C471.642 397.14 471.642 397.14 470.131 398.43C466.535 401.437 462.77 404.217 458.999 407C457.818 386.547 460.072 368.685 465.247 348.945C466.507 344.097 467.61 339.237 468.615 334.329C469.314 330.914 470.05 327.508 470.788 324.101C471.133 322.466 471.464 320.829 471.778 319.188C474.22 306.557 474.22 306.557 478.184 303.086C481.078 301.728 483.923 300.869 486.999 300C488.826 299.275 490.653 298.548 492.467 297.792C494.482 296.954 496.506 296.145 498.535 295.342C499.242 295.062 499.948 294.783 500.676 294.494C501.402 294.207 502.127 293.921 502.874 293.625C505.968 292.398 509.062 291.168 512.155 289.937C512.951 289.621 513.746 289.304 514.566 288.978C520.508 286.609 526.428 284.189 532.342 281.75C534.036 281.054 535.731 280.358 537.425 279.662C539.887 278.65 542.348 277.635 544.806 276.612C547.083 275.663 549.364 274.725 551.647 273.789C552.319 273.505 552.992 273.222 553.685 272.929C557.241 271.483 560.153 270.504 563.999 271Z" fill="#666666"/>
|
||||
<path d="M78.8286 272.929C79.8159 273.312 80.8031 273.695 81.8203 274.09C82.9747 274.536 84.129 274.982 85.3184 275.442C86.5874 275.94 87.8563 276.439 89.125 276.938C90.459 277.458 91.793 277.978 93.127 278.498C109.42 284.875 125.599 291.54 141.781 298.191C142.609 298.532 143.437 298.872 144.29 299.222C145.826 299.855 147.358 300.493 148.887 301.14C150.259 301.698 151.648 302.217 153.052 302.687C156.374 303.859 157.775 304.553 159.386 307.751C159.627 308.87 159.869 309.988 160.117 311.141C160.41 312.419 160.702 313.697 161.004 315.014C161.295 316.426 161.586 317.838 161.875 319.25C162.197 320.723 162.521 322.195 162.848 323.667C163.364 326.001 163.878 328.335 164.379 330.672C165.619 336.423 167.027 342.115 168.508 347.809C173.668 367.932 176.393 386.181 175 407C170.552 403.733 166.234 400.451 162.188 396.688C157.88 392.754 153.437 389.043 148.906 385.371C141.721 379.509 134.691 373.458 127.661 367.411C122.117 362.643 116.522 357.953 110.844 353.344C106.909 350.101 103.054 346.77 99.1914 343.441C95.6668 340.41 92.1095 337.43 88.5 334.5C83.654 330.566 78.919 326.516 74.1914 322.441C69.8095 318.673 65.3547 315.005 60.8672 311.363C56.8128 308.022 52.8328 304.595 48.8574 301.16C48.2986 300.678 47.7398 300.196 47.1641 299.699C46.6307 299.238 46.0974 298.777 45.5479 298.302C44.0962 297.081 42.5923 295.923 41.0859 294.77C40.3976 294.186 39.7092 293.602 39 293C39 292.34 39 291.68 39 291C40.5537 289.798 40.5537 289.798 42.7344 288.461C43.5308 287.966 44.3272 287.471 45.1477 286.962C46.0065 286.438 46.8652 285.914 47.75 285.375C54.8402 281.043 54.8402 281.043 61.7227 276.395C62.7423 275.666 63.762 274.938 64.8125 274.188C65.6852 273.542 66.5579 272.896 67.457 272.23C71.4436 270.302 74.7407 271.586 78.8286 272.929Z" fill="#666666"/>
|
||||
<path d="M442 398C442.99 398.495 442.99 398.495 444 399C443.585 399.648 443.169 400.297 442.741 400.965C438.171 408.12 433.692 415.327 429.25 422.562C428.546 423.709 427.841 424.855 427.137 426.001C422.68 433.253 418.233 440.51 413.802 447.778C410.577 453.066 407.331 458.341 404.083 463.616C402.113 466.816 400.144 470.017 398.176 473.219C396.888 475.313 395.6 477.406 394.312 479.5C393.97 480.057 393.627 480.614 393.274 481.188C388.14 489.534 382.994 497.873 377.816 506.192C374.896 510.896 372.022 515.623 369.188 520.379C368.552 521.441 367.916 522.503 367.26 523.597C366.026 525.661 364.799 527.73 363.58 529.803C363.025 530.729 362.47 531.655 361.898 532.609C361.41 533.434 360.922 534.259 360.419 535.108C359 537 359 537 356 538C353.345 531.616 350.694 525.229 348.047 518.841C347.147 516.671 346.246 514.501 345.344 512.332C344.043 509.203 342.746 506.072 341.449 502.941C341.049 501.982 340.649 501.022 340.237 500.033C337.919 494.422 335.827 488.79 334 483C334.969 482.285 335.939 481.569 336.938 480.832C344.75 475.036 352.41 469.104 359.926 462.929C364.707 459.014 369.574 455.231 374.5 451.5C380.86 446.681 387.085 441.724 393.257 436.668C398.362 432.496 403.553 428.457 408.793 424.457C413.967 420.505 419.068 416.461 424.181 412.431C426.093 410.927 428.009 409.428 429.926 407.93C430.566 407.429 431.206 406.928 431.865 406.412C433.096 405.449 434.327 404.488 435.558 403.528C437.811 401.763 439.974 400.026 442 398Z" fill="#666666"/>
|
||||
<path d="M190 398C192.785 399.298 195.072 400.727 197.434 402.688C198.397 403.481 198.397 403.481 199.379 404.29C200.059 404.854 200.738 405.418 201.438 406C207.475 410.964 213.58 415.801 219.793 420.543C223.199 423.144 226.57 425.787 229.938 428.438C230.522 428.897 231.106 429.357 231.708 429.831C233.473 431.22 235.236 432.61 237 434C239.249 435.772 241.499 437.542 243.75 439.312C244.319 439.76 244.887 440.207 245.473 440.668C248.746 443.24 252.028 445.8 255.32 448.348C262.738 454.091 270.116 459.869 277.375 465.812C284.705 471.813 292.112 477.741 300 483C299.37 485.73 298.564 488.254 297.497 490.844C297.193 491.586 296.889 492.327 296.576 493.092C296.082 494.284 296.082 494.284 295.578 495.5C295.236 496.332 294.894 497.163 294.542 498.02C293.447 500.681 292.349 503.34 291.25 506C290.526 507.759 289.802 509.518 289.078 511.277C285.408 520.194 281.72 529.104 278 538C274.084 536.695 273.637 535.207 271.551 531.691C270.538 530.001 270.538 530.001 269.504 528.277C268.774 527.039 268.043 525.801 267.312 524.562C266.554 523.293 265.795 522.023 265.035 520.754C263.86 518.793 262.685 516.832 261.514 514.869C257.613 508.332 253.626 501.851 249.625 495.375C248.294 493.216 246.963 491.057 245.633 488.898C245.011 487.889 244.388 486.879 243.747 485.839C241.322 481.898 238.91 477.95 236.5 474C230.76 464.593 224.982 455.211 219.203 445.828C216.376 441.237 213.553 436.643 210.734 432.047C201.226 416.548 201.226 416.548 191.406 401.246C190 399 190 399 190 398Z" fill="#666666"/>
|
||||
<path d="M268 49.9999C268.92 50.0706 269.84 50.1414 270.788 50.2143C285.285 51.2198 299.794 51.1534 314.317 51.1303C317.986 51.1245 321.655 51.1279 325.324 51.1339C328.182 51.1375 331.039 51.1363 333.897 51.1337C335.902 51.1327 337.906 51.1358 339.911 51.1389C345.718 51.1239 351.281 51.0212 357 49.9999C358.645 49.9394 360.292 49.9121 361.938 49.9374C362.71 49.9464 363.482 49.9555 364.277 49.9648C364.846 49.9764 365.414 49.988 366 49.9999C362.663 52.7852 359.338 55.072 355.535 57.1718C354.494 57.7504 353.453 58.329 352.381 58.9252C350.738 59.8284 350.738 59.8284 349.062 60.7499C340.239 65.5685 340.239 65.5685 331.594 70.6952C330.98 71.072 330.366 71.4487 329.734 71.8368C328.2 72.7897 326.679 73.7649 325.16 74.7421C319.923 76.8264 314.919 77.1283 309.6 75.0722C307.093 73.7557 304.708 72.3218 302.312 70.8124C300.493 69.7187 298.673 68.6263 296.852 67.5351C295.435 66.6729 295.435 66.6729 293.989 65.7934C288.784 62.6703 283.482 59.7195 278.188 56.7499C276.222 55.641 274.257 54.5317 272.293 53.4218C270.862 52.6144 269.431 51.8071 268 50.9999C268 50.6699 268 50.3399 268 49.9999Z" fill="#666666"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -8,6 +8,91 @@ Date: 2025-12-26
|
||||
All notable changes to this project.
|
||||

|
||||
|
||||
## v1.31.1
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Image support in footer component**
|
||||
|
||||
- Footer markdown now supports images using standard markdown syntax or HTML
|
||||
- Images can be sized using `width`, `height`, `style`, or `class` HTML attributes
|
||||
- Image attributes are sanitized by rehypeSanitize for security (removes dangerous CSS)
|
||||
- Footer images support lazy loading and optional captions from alt text
|
||||
- CSS styles added for footer images
|
||||
|
||||
Updated files: `src/components/Footer.tsx`, `src/styles/global.css`
|
||||
|
||||
## v1.31.0
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Customizable footer component with markdown support**
|
||||
|
||||
- New `Footer` component that renders markdown content
|
||||
- Footer content can be set in frontmatter `footer` field (markdown) or use `siteConfig.footer.defaultContent`
|
||||
- Footer can be enabled/disabled globally via `siteConfig.footer.enabled`
|
||||
- Footer visibility controlled per-page type via `siteConfig.footer.showOnHomepage`, `showOnPosts`, `showOnPages`
|
||||
- New `showFooter` frontmatter field for posts and pages to override siteConfig defaults
|
||||
- New `footer` frontmatter field for posts and pages to provide custom markdown content
|
||||
- Footer renders inside article at bottom for posts/pages, maintains current position on homepage
|
||||
- Footer supports markdown formatting (links, paragraphs, line breaks)
|
||||
- Sidebars flush to bottom when footer is enabled
|
||||
|
||||
Updated files: `src/components/Footer.tsx`, `src/pages/Home.tsx`, `src/pages/Post.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `convex/schema.ts`, `convex/posts.ts`, `convex/pages.ts`, `scripts/sync-posts.ts`, `src/pages/Write.tsx`
|
||||
|
||||
Documentation updated: `files.md`, `changelog.md`
|
||||
|
||||
## v1.30.2
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Right sidebar default behavior fix**
|
||||
|
||||
- Right sidebar no longer appears on pages/posts without explicit `rightSidebar: true` in frontmatter
|
||||
- Changed default behavior: right sidebar is now opt-in only
|
||||
- Pages like About and Contact now render without the right sidebar as expected
|
||||
- `CopyPageDropdown` correctly appears in nav bar when right sidebar is disabled
|
||||
- Logic in `Post.tsx` changed from `(page.rightSidebar ?? true)` to `page.rightSidebar === true`
|
||||
|
||||
Updated files: `src/pages/Post.tsx`
|
||||
|
||||
## v1.30.1
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**TypeScript error fix**
|
||||
|
||||
- TypeScript error in `convex/posts.ts` where `rightSidebar` was used in mutation handlers but missing from args validators
|
||||
- Added `rightSidebar: v.optional(v.boolean())` to `syncPosts` args validator
|
||||
- Added `rightSidebar: v.optional(v.boolean())` to `syncPostsPublic` args validator
|
||||
|
||||
Updated files: `convex/posts.ts`
|
||||
|
||||
## v1.30.0
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
**Right sidebar feature for posts and pages**
|
||||
|
||||
- New `RightSidebar` component that displays `CopyPageDropdown` in a right sidebar
|
||||
- Appears at 1135px+ viewport width when enabled
|
||||
- Controlled by `siteConfig.rightSidebar.enabled` (global toggle)
|
||||
- Per-post/page control via `rightSidebar: true` frontmatter field (opt-in only)
|
||||
- Three-column layout support: left sidebar (TOC), main content, right sidebar (CopyPageDropdown)
|
||||
- CopyPageDropdown automatically moves from nav to right sidebar when enabled
|
||||
- Responsive: right sidebar hidden below 1135px, CopyPageDropdown returns to nav
|
||||
- Right sidebar configuration in siteConfig
|
||||
- `rightSidebar.enabled`: Global toggle for right sidebar feature
|
||||
- `rightSidebar.minWidth`: Minimum viewport width to show sidebar (default: 1135px)
|
||||
- `rightSidebar` frontmatter field
|
||||
- Available for both blog posts and pages
|
||||
- Optional boolean field to enable/disable right sidebar per post/page
|
||||
- Added to Write page frontmatter reference with copy button
|
||||
|
||||
Updated files: `src/components/RightSidebar.tsx`, `src/pages/Post.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `convex/schema.ts`, `convex/posts.ts`, `convex/pages.ts`, `scripts/sync-posts.ts`, `src/pages/Write.tsx`
|
||||
|
||||
Documentation updated: `content/blog/setup-guide.md`, `content/pages/docs.md`, `files.md`, `changelog.md`
|
||||
|
||||
## v1.29.0
|
||||
|
||||
Released December 25, 2025
|
||||
|
||||
@@ -57,6 +57,8 @@ interface ParsedPost {
|
||||
authorImage?: string; // Author avatar image URL (round)
|
||||
layout?: string; // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
|
||||
showFooter?: boolean; // Show footer on this post (overrides siteConfig default)
|
||||
footer?: string; // Footer markdown content (overrides siteConfig defaultContent)
|
||||
}
|
||||
|
||||
// Page frontmatter (for static pages like About, Projects, Contact)
|
||||
@@ -74,6 +76,7 @@ interface PageFrontmatter {
|
||||
authorImage?: string; // Author avatar image URL (round)
|
||||
layout?: string; // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
|
||||
showFooter?: boolean; // Show footer on this page (overrides siteConfig default)
|
||||
}
|
||||
|
||||
interface ParsedPage {
|
||||
@@ -91,6 +94,7 @@ interface ParsedPage {
|
||||
authorImage?: string; // Author avatar image URL (round)
|
||||
layout?: string; // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
|
||||
showFooter?: boolean; // Show footer on this page (overrides siteConfig default)
|
||||
}
|
||||
|
||||
// Calculate reading time based on word count
|
||||
@@ -132,6 +136,8 @@ function parseMarkdownFile(filePath: string): ParsedPost | null {
|
||||
authorImage: frontmatter.authorImage, // Author avatar image URL
|
||||
layout: frontmatter.layout, // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar: frontmatter.rightSidebar, // Enable right sidebar with CopyPageDropdown
|
||||
showFooter: frontmatter.showFooter, // Show footer on this post
|
||||
footer: frontmatter.footer, // Footer markdown content
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error parsing ${filePath}:`, error);
|
||||
@@ -184,6 +190,7 @@ function parsePageFile(filePath: string): ParsedPage | null {
|
||||
authorImage: frontmatter.authorImage, // Author avatar image URL
|
||||
layout: frontmatter.layout, // Layout type: "sidebar" for docs-style layout
|
||||
rightSidebar: frontmatter.rightSidebar, // Enable right sidebar with CopyPageDropdown
|
||||
showFooter: frontmatter.showFooter, // Show footer on this page
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error parsing page ${filePath}:`, error);
|
||||
|
||||
@@ -285,6 +285,29 @@ function getTextContent(children: React.ReactNode): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Anchor link component for headings
|
||||
function HeadingAnchor({ id }: { id: string }) {
|
||||
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
// Copy URL to clipboard, but allow default scroll behavior
|
||||
const url = `${window.location.origin}${window.location.pathname}#${id}`;
|
||||
navigator.clipboard.writeText(url).catch(() => {
|
||||
// Silently fail if clipboard API is not available
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`#${id}`}
|
||||
className="heading-anchor"
|
||||
onClick={handleClick}
|
||||
aria-label="Copy link to heading"
|
||||
title="Copy link to heading"
|
||||
>
|
||||
#
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BlogPost({ content }: BlogPostProps) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -397,6 +420,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h1 id={id} className="blog-h1">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
@@ -405,6 +429,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h2 id={id} className="blog-h2">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
@@ -413,6 +438,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h3 id={id} className="blog-h3">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
@@ -421,6 +447,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h4 id={id} className="blog-h4">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
@@ -429,6 +456,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h5 id={id} className="blog-h5">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
@@ -437,6 +465,7 @@ export default function BlogPost({ content }: BlogPostProps) {
|
||||
const id = generateSlug(getTextContent(children));
|
||||
return (
|
||||
<h6 id={id} className="blog-h6">
|
||||
<HeadingAnchor id={id} />
|
||||
{children}
|
||||
</h6>
|
||||
);
|
||||
|
||||
91
src/components/Footer.tsx
Normal file
91
src/components/Footer.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
||||
import siteConfig from "../config/siteConfig";
|
||||
|
||||
// Sanitize schema for footer markdown (allows links, paragraphs, line breaks, images)
|
||||
// style attribute is sanitized by rehypeSanitize to remove dangerous CSS
|
||||
const footerSanitizeSchema = {
|
||||
...defaultSchema,
|
||||
tagNames: [...(defaultSchema.tagNames || []), "br", "img"],
|
||||
attributes: {
|
||||
...defaultSchema.attributes,
|
||||
img: ["src", "alt", "loading", "width", "height", "style", "class"],
|
||||
},
|
||||
};
|
||||
|
||||
// Footer component
|
||||
// Renders markdown content from frontmatter footer field
|
||||
// Falls back to siteConfig.footer.defaultContent if no frontmatter footer provided
|
||||
// Visibility controlled by siteConfig.footer settings and frontmatter showFooter field
|
||||
interface FooterProps {
|
||||
content?: string; // Markdown content from frontmatter
|
||||
}
|
||||
|
||||
export default function Footer({ content }: FooterProps) {
|
||||
const { footer } = siteConfig;
|
||||
|
||||
// Don't render if footer is globally disabled
|
||||
if (!footer.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use frontmatter content if provided, otherwise fall back to siteConfig default
|
||||
const footerContent = content || footer.defaultContent;
|
||||
|
||||
// Don't render if no content available
|
||||
if (!footerContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="site-footer">
|
||||
<div className="site-footer-content">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
rehypePlugins={[rehypeRaw, [rehypeSanitize, footerSanitizeSchema]]}
|
||||
components={{
|
||||
p({ children }) {
|
||||
return <p className="site-footer-text">{children}</p>;
|
||||
},
|
||||
img({ src, alt, width, height, style, className }) {
|
||||
return (
|
||||
<span className="site-footer-image-wrapper">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt || ""}
|
||||
className={className || "site-footer-image"}
|
||||
loading="lazy"
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
/>
|
||||
{alt && (
|
||||
<span className="site-footer-image-caption">{alt}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
a({ href, children }) {
|
||||
const isExternal = href?.startsWith("http");
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
rel={isExternal ? "noopener noreferrer" : undefined}
|
||||
className="site-footer-link"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{footerContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,10 @@
|
||||
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) {
|
||||
// Right sidebar component - maintains layout spacing when sidebars are enabled
|
||||
// CopyPageDropdown is now rendered in the main content area instead
|
||||
export default function RightSidebar() {
|
||||
return (
|
||||
<aside className="post-sidebar-right">
|
||||
<div className="right-sidebar-content">
|
||||
<CopyPageDropdown {...props} />
|
||||
{/* Empty - CopyPageDropdown moved to main content area */}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -87,6 +87,17 @@ export interface RightSidebarConfig {
|
||||
minWidth?: number; // Minimum viewport width to show sidebar (default: 1135)
|
||||
}
|
||||
|
||||
// Footer configuration
|
||||
// Footer content can be set in frontmatter (footer field) or use defaultContent here
|
||||
// Footer can be enabled/disabled globally and per-page via frontmatter showFooter field
|
||||
export interface FooterConfig {
|
||||
enabled: boolean; // Global toggle for footer
|
||||
showOnHomepage: boolean; // Show footer on homepage
|
||||
showOnPosts: boolean; // Default: show footer on blog posts
|
||||
showOnPages: boolean; // Default: show footer on static pages
|
||||
defaultContent?: string; // Default markdown content if no frontmatter footer field provided
|
||||
}
|
||||
|
||||
// Site configuration interface
|
||||
export interface SiteConfig {
|
||||
// Basic site info
|
||||
@@ -136,6 +147,9 @@ export interface SiteConfig {
|
||||
|
||||
// Right sidebar configuration
|
||||
rightSidebar: RightSidebarConfig;
|
||||
|
||||
// Footer configuration
|
||||
footer: FooterConfig;
|
||||
}
|
||||
|
||||
// Default site configuration
|
||||
@@ -289,6 +303,20 @@ export const siteConfig: SiteConfig = {
|
||||
enabled: true, // Set to false to disable right sidebar globally
|
||||
minWidth: 1135, // Minimum viewport width in pixels to show sidebar
|
||||
},
|
||||
|
||||
// Footer configuration
|
||||
// Footer content can be set in frontmatter (footer field) or use defaultContent here
|
||||
// Use showFooter: false in frontmatter to hide footer on specific posts/pages
|
||||
footer: {
|
||||
enabled: true, // Global toggle for footer
|
||||
showOnHomepage: true, // Show footer on homepage
|
||||
showOnPosts: true, // Default: show footer on blog posts (override with frontmatter)
|
||||
showOnPages: true, // Default: show footer on static pages (override with frontmatter)
|
||||
// Default footer markdown (used when frontmatter footer field is not provided)
|
||||
defaultContent: `Built with [Convex](https://convex.dev) for real-time sync and deployed on [Netlify](https://netlify.com). Read the [project on GitHub](https://github.com/waynesutton/markdown-site) to fork and deploy your own. View [real-time site stats](/stats).
|
||||
|
||||
Created by [Wayne](https://x.com/waynesutton) with Convex, Cursor, and Claude Opus 4.5. Follow on [Twitter/X](https://x.com/waynesutton), [LinkedIn](https://www.linkedin.com/in/waynesutton/), and [GitHub](https://github.com/waynesutton). This project is licensed under the MIT [License](https://github.com/waynesutton/markdown-site?tab=MIT-1-ov-file).`,
|
||||
},
|
||||
};
|
||||
|
||||
// Export the config as default for easy importing
|
||||
|
||||
@@ -6,6 +6,7 @@ import PostList from "../components/PostList";
|
||||
import FeaturedCards from "../components/FeaturedCards";
|
||||
import LogoMarquee from "../components/LogoMarquee";
|
||||
import GitHubContributions from "../components/GitHubContributions";
|
||||
import Footer from "../components/Footer";
|
||||
import siteConfig from "../config/siteConfig";
|
||||
|
||||
// Local storage key for view mode preference
|
||||
@@ -223,82 +224,9 @@ export default function Home() {
|
||||
{renderLogoGallery("above-footer")}
|
||||
|
||||
{/* Footer section */}
|
||||
<section className="home-footer">
|
||||
<p className="home-footer-text">
|
||||
Built with{" "}
|
||||
<a
|
||||
href={siteConfig.links.convex}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Convex
|
||||
</a>{" "}
|
||||
for real-time sync and deployed on{" "}
|
||||
<a
|
||||
href={siteConfig.links.netlify}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Netlify
|
||||
</a>
|
||||
. Read the{" "}
|
||||
<a
|
||||
href="https://github.com/waynesutton/markdown-site"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
project on GitHub
|
||||
</a>{" "}
|
||||
to fork and deploy your own. View{" "}
|
||||
<Link to="/stats" className="home-text-link">
|
||||
real-time site stats
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<p></p>
|
||||
<br></br>
|
||||
<p className="home-footer-text">
|
||||
Created by{" "}
|
||||
<a
|
||||
href="https://x.com/waynesutton"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Wayne
|
||||
</a>{" "}
|
||||
with Convex, Cursor, and Claude Opus 4.5. Follow on{" "}
|
||||
<a
|
||||
href="https://x.com/waynesutton"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Twitter/X
|
||||
</a>
|
||||
,{" "}
|
||||
<a
|
||||
href="https://www.linkedin.com/in/waynesutton/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
, and{" "}
|
||||
<a
|
||||
href="https://github.com/waynesutton"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
. This project is licensed under the MIT{" "}
|
||||
<a
|
||||
href="https://github.com/waynesutton/markdown-site?tab=MIT-1-ov-file"
|
||||
className="home-text-link"
|
||||
>
|
||||
License.
|
||||
</a>{" "}
|
||||
</p>
|
||||
</section>
|
||||
{siteConfig.footer.enabled && siteConfig.footer.showOnHomepage && (
|
||||
<Footer content={siteConfig.footer.defaultContent} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import BlogPost from "../components/BlogPost";
|
||||
import CopyPageDropdown from "../components/CopyPageDropdown";
|
||||
import PageSidebar from "../components/PageSidebar";
|
||||
import RightSidebar from "../components/RightSidebar";
|
||||
import Footer from "../components/Footer";
|
||||
import { extractHeadings } from "../utils/extractHeadings";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { format, parseISO } from "date-fns";
|
||||
@@ -195,12 +196,15 @@ export default function Post() {
|
||||
return (
|
||||
<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>
|
||||
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
|
||||
{!hasRightSidebar && (
|
||||
{/* Hide back-button when sidebars are enabled */}
|
||||
{!hasAnySidebar && (
|
||||
<button onClick={() => navigate("/")} className="back-button">
|
||||
<ArrowLeft size={16} />
|
||||
<span>Back</span>
|
||||
</button>
|
||||
)}
|
||||
{/* Only show CopyPageDropdown in nav if no sidebars are enabled */}
|
||||
{!hasAnySidebar && (
|
||||
<CopyPageDropdown
|
||||
title={page.title}
|
||||
content={page.content}
|
||||
@@ -222,7 +226,21 @@ export default function Post() {
|
||||
{/* Main content */}
|
||||
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
|
||||
<header className="post-header">
|
||||
<h1 className="post-title">{page.title}</h1>
|
||||
<div className="post-title-row">
|
||||
<h1 className="post-title">{page.title}</h1>
|
||||
{/* Show CopyPageDropdown aligned with title when sidebars are enabled */}
|
||||
{hasAnySidebar && (
|
||||
<div className="post-header-actions">
|
||||
<CopyPageDropdown
|
||||
title={page.title}
|
||||
content={page.content}
|
||||
url={window.location.href}
|
||||
slug={page.slug}
|
||||
description={page.excerpt}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Author avatar and name for pages (optional) */}
|
||||
{(page.authorImage || page.authorName) && (
|
||||
<div className="post-meta-header">
|
||||
@@ -243,18 +261,16 @@ export default function Post() {
|
||||
</header>
|
||||
|
||||
<BlogPost content={page.content} />
|
||||
|
||||
{/* Footer - shown inside article at bottom for pages */}
|
||||
{siteConfig.footer.enabled &&
|
||||
(page.showFooter !== undefined ? page.showFooter : siteConfig.footer.showOnPages) && (
|
||||
<Footer content={page.footer} />
|
||||
)}
|
||||
</article>
|
||||
|
||||
{/* Right sidebar - CopyPageDropdown */}
|
||||
{hasRightSidebar && (
|
||||
<RightSidebar
|
||||
title={page.title}
|
||||
content={page.content}
|
||||
url={window.location.href}
|
||||
slug={page.slug}
|
||||
description={page.excerpt}
|
||||
/>
|
||||
)}
|
||||
{/* Right sidebar - empty when sidebars are enabled, CopyPageDropdown moved to main content */}
|
||||
{hasRightSidebar && <RightSidebar />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -303,12 +319,15 @@ export default function Post() {
|
||||
return (
|
||||
<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>
|
||||
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
|
||||
{!hasRightSidebar && (
|
||||
{/* Hide back-button when sidebars are enabled */}
|
||||
{!hasAnySidebar && (
|
||||
<button onClick={() => navigate("/")} className="back-button">
|
||||
<ArrowLeft size={16} />
|
||||
<span>Back</span>
|
||||
</button>
|
||||
)}
|
||||
{/* Only show CopyPageDropdown in nav if no sidebars are enabled */}
|
||||
{!hasAnySidebar && (
|
||||
<CopyPageDropdown
|
||||
title={post.title}
|
||||
content={post.content}
|
||||
@@ -332,7 +351,24 @@ export default function Post() {
|
||||
|
||||
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
|
||||
<header className="post-header">
|
||||
<h1 className="post-title">{post.title}</h1>
|
||||
<div className="post-title-row">
|
||||
<h1 className="post-title">{post.title}</h1>
|
||||
{/* Show CopyPageDropdown aligned with title when sidebars are enabled */}
|
||||
{hasAnySidebar && (
|
||||
<div className="post-header-actions">
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="post-meta-header">
|
||||
{/* Author avatar and name (optional) */}
|
||||
{(post.authorImage || post.authorName) && (
|
||||
@@ -431,21 +467,16 @@ export default function Post() {
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
|
||||
{/* Footer - shown inside article at bottom for posts */}
|
||||
{siteConfig.footer.enabled &&
|
||||
(post.showFooter !== undefined ? post.showFooter : siteConfig.footer.showOnPosts) && (
|
||||
<Footer content={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}
|
||||
/>
|
||||
)}
|
||||
{/* Right sidebar - empty when sidebars are enabled, CopyPageDropdown moved to main content */}
|
||||
{hasRightSidebar && <RightSidebar />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -40,6 +40,8 @@ const POST_FIELDS = [
|
||||
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
|
||||
{ name: "layout", required: false, example: '"sidebar"' },
|
||||
{ name: "rightSidebar", required: false, example: "true" },
|
||||
{ name: "showFooter", required: false, example: "true" },
|
||||
{ name: "footer", required: false, example: '"Built with [Convex](https://convex.dev)."' },
|
||||
];
|
||||
|
||||
// Frontmatter field definitions for pages
|
||||
@@ -57,6 +59,8 @@ const PAGE_FIELDS = [
|
||||
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
|
||||
{ name: "layout", required: false, example: '"sidebar"' },
|
||||
{ name: "rightSidebar", required: false, example: "true" },
|
||||
{ name: "showFooter", required: false, example: "true" },
|
||||
{ name: "footer", required: false, example: '"Built with [Convex](https://convex.dev)."' },
|
||||
];
|
||||
|
||||
// Generate frontmatter template based on content type
|
||||
|
||||
@@ -314,7 +314,7 @@ body {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px;
|
||||
/* padding: 40px 24px; */
|
||||
}
|
||||
|
||||
/* Wide content layout for pages that need more space (stats, etc.) */
|
||||
@@ -648,7 +648,7 @@ body {
|
||||
|
||||
/* Post page styles */
|
||||
.post-page {
|
||||
padding-top: 20px;
|
||||
padding-top: 55px;
|
||||
}
|
||||
|
||||
/* Full-width sidebar layout - breaks out of .main-content constraints */
|
||||
@@ -656,7 +656,7 @@ body {
|
||||
padding-top: 20px;
|
||||
/* Break out of the 680px max-width container */
|
||||
width: calc(100vw - 48px);
|
||||
max-width: 1400px;
|
||||
max-width: none;
|
||||
margin-left: calc(-1 * (min(100vw - 48px, 1400px) - 680px) / 2);
|
||||
position: relative;
|
||||
/* Add left padding to align content with sidebar edge
|
||||
@@ -862,6 +862,22 @@ body {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
/* Title row container - flex layout for title and CopyPageDropdown */
|
||||
.post-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Header actions container for CopyPageDropdown when sidebars are enabled */
|
||||
.post-header-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* Sidebar layout for pages - two-column grid with docs-style sidebar */
|
||||
.post-content-with-sidebar {
|
||||
display: grid;
|
||||
@@ -888,7 +904,7 @@ body {
|
||||
.post-sidebar-wrapper {
|
||||
position: sticky;
|
||||
top: 80px;
|
||||
align-self: flex-start;
|
||||
align-self: start;
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
/* Hide scrollbar while keeping scroll functionality */
|
||||
@@ -902,7 +918,7 @@ body {
|
||||
padding-bottom: 24px;
|
||||
margin-top: -24px;
|
||||
border-radius: 6px;
|
||||
/* Extend background to fill height */
|
||||
/* Extend background to fill height - ensures sidebars flush to bottom */
|
||||
min-height: calc(100vh - 80px);
|
||||
/* Top border using CSS variable for theme consistency */
|
||||
border-top: 1px solid var(--border-sidebar);
|
||||
@@ -923,7 +939,7 @@ body {
|
||||
.post-sidebar-right {
|
||||
position: sticky;
|
||||
top: 80px;
|
||||
align-self: flex-start;
|
||||
align-self: start;
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
/* Hide scrollbar while keeping scroll functionality */
|
||||
@@ -937,7 +953,7 @@ body {
|
||||
padding-bottom: 24px;
|
||||
margin-top: -24px;
|
||||
border-radius: 6px;
|
||||
/* Extend background to fill height */
|
||||
/* Extend background to fill height - ensures sidebars flush to bottom */
|
||||
min-height: calc(100vh - 80px);
|
||||
/* Top border using CSS variable for theme consistency */
|
||||
border-top: 1px solid var(--border-sidebar);
|
||||
@@ -1177,8 +1193,10 @@ body {
|
||||
font-size: var(--font-size-post-title);
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 0;
|
||||
line-height: 1.2;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-meta-header {
|
||||
@@ -1238,6 +1256,7 @@ body {
|
||||
margin: 48px 0 24px;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-h2 {
|
||||
@@ -1246,6 +1265,7 @@ body {
|
||||
margin: 40px 0 20px;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-h3 {
|
||||
@@ -1253,6 +1273,7 @@ body {
|
||||
font-weight: 300;
|
||||
margin: 32px 0 16px;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-h4 {
|
||||
@@ -1260,6 +1281,7 @@ body {
|
||||
font-weight: 300;
|
||||
margin: 24px 0 12px;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-h5 {
|
||||
@@ -1267,6 +1289,7 @@ body {
|
||||
font-weight: 300;
|
||||
margin: 20px 0 10px;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-h6 {
|
||||
@@ -1274,6 +1297,33 @@ body {
|
||||
font-weight: 300;
|
||||
margin: 16px 0 8px;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Anchor links for headings */
|
||||
.heading-anchor {
|
||||
position: absolute;
|
||||
left: -1.5em;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.blog-h1:hover .heading-anchor,
|
||||
.blog-h2:hover .heading-anchor,
|
||||
.blog-h3:hover .heading-anchor,
|
||||
.blog-h4:hover .heading-anchor,
|
||||
.blog-h5:hover .heading-anchor,
|
||||
.blog-h6:hover .heading-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.heading-anchor:hover {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.blog-link {
|
||||
@@ -1716,6 +1766,72 @@ body {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Site footer styles (used by Footer component) */
|
||||
.site-footer {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 4rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.site-footer-content {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-footer-text);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.site-footer-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-footer-text);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.site-footer-text:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.site-footer-text a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-footer-text a:hover {
|
||||
color: var(--link-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.site-footer-link {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-footer-link:hover {
|
||||
color: var(--link-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Site footer image styles */
|
||||
.site-footer-image-wrapper {
|
||||
/* display: block;*/
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.site-footer-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.site-footer-image-caption {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Blog Page Styles
|
||||
Dedicated /blog page that shows all posts
|
||||
@@ -2009,6 +2125,12 @@ body {
|
||||
font-size: var(--font-size-blog-h6);
|
||||
}
|
||||
|
||||
/* Adjust anchor link position on mobile */
|
||||
.heading-anchor {
|
||||
left: -1.2em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Table mobile styles */
|
||||
.blog-table {
|
||||
font-size: var(--font-size-table);
|
||||
|
||||
Reference in New Issue
Block a user