Files
wiki/convex/schema.ts
Wayne Sutton 0342539aac feat: add sidebar layout support for blog posts
- Add layout field to posts schema and frontmatter
- Enable docs-style sidebar layout for posts (previously pages only)
- Update Post.tsx to handle sidebar for both posts and pages
- Add layout field to Write.tsx frontmatter reference
- Update documentation in docs.md, setup-guide.md, how-to-publish.md
- Add documentation links to README.md
2025-12-23 00:57:21 -08:00

102 lines
3.4 KiB
TypeScript

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
// Blog posts table
posts: defineTable({
slug: v.string(),
title: v.string(),
description: v.string(),
content: v.string(),
date: v.string(),
published: v.boolean(),
tags: v.array(v.string()),
readTime: v.optional(v.string()),
image: v.optional(v.string()), // Header/OG image URL
excerpt: v.optional(v.string()), // Short excerpt for card view
featured: v.optional(v.boolean()), // Show in featured section
featuredOrder: v.optional(v.number()), // Order in featured section (lower = first)
authorName: v.optional(v.string()), // Author display name
authorImage: v.optional(v.string()), // Author avatar image URL (round)
layout: v.optional(v.string()), // Layout type: "sidebar" for docs-style layout
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
.index("by_date", ["date"])
.index("by_published", ["published"])
.index("by_featured", ["featured"])
.searchIndex("search_content", {
searchField: "content",
filterFields: ["published"],
})
.searchIndex("search_title", {
searchField: "title",
filterFields: ["published"],
}),
// Static pages (about, projects, contact, etc.)
pages: defineTable({
slug: v.string(),
title: v.string(),
content: v.string(),
published: v.boolean(),
order: v.optional(v.number()), // Display order in nav
excerpt: v.optional(v.string()), // Short excerpt for card view
image: v.optional(v.string()), // Thumbnail/OG image URL for featured cards
featured: v.optional(v.boolean()), // Show in featured section
featuredOrder: v.optional(v.number()), // Order in featured section (lower = first)
authorName: v.optional(v.string()), // Author display name
authorImage: v.optional(v.string()), // Author avatar image URL (round)
layout: v.optional(v.string()), // Layout type: "sidebar" for docs-style layout
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
.index("by_published", ["published"])
.index("by_featured", ["featured"])
.searchIndex("search_content", {
searchField: "content",
filterFields: ["published"],
})
.searchIndex("search_title", {
searchField: "title",
filterFields: ["published"],
}),
// View counts for analytics
viewCounts: defineTable({
slug: v.string(),
count: v.number(),
}).index("by_slug", ["slug"]),
// Site configuration (about content, links, etc.)
siteConfig: defineTable({
key: v.string(),
value: v.any(),
}).index("by_key", ["key"]),
// Page view events for analytics (event records pattern)
pageViews: defineTable({
path: v.string(),
pageType: v.string(), // "blog" | "page" | "home" | "stats"
sessionId: v.string(),
timestamp: v.number(),
})
.index("by_path", ["path"])
.index("by_timestamp", ["timestamp"])
.index("by_session_path", ["sessionId", "path"]),
// Active sessions for real-time visitor tracking
activeSessions: defineTable({
sessionId: v.string(),
currentPath: v.string(),
lastSeen: v.number(),
// Location data (optional, from Netlify geo headers)
city: v.optional(v.string()),
country: v.optional(v.string()),
latitude: v.optional(v.number()),
longitude: v.optional(v.number()),
})
.index("by_sessionId", ["sessionId"])
.index("by_lastSeen", ["lastSeen"]),
});