mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
feat: Add docsSectionGroupOrder frontmatter field for controlling docs sidebar group order
This commit is contained in:
@@ -184,6 +184,7 @@ export const getPageBySlug = query({
|
||||
contactForm: v.optional(v.boolean()),
|
||||
newsletter: v.optional(v.boolean()),
|
||||
textAlign: v.optional(v.string()),
|
||||
docsSection: v.optional(v.boolean()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
@@ -221,6 +222,94 @@ export const getPageBySlug = query({
|
||||
contactForm: page.contactForm,
|
||||
newsletter: page.newsletter,
|
||||
textAlign: page.textAlign,
|
||||
docsSection: page.docsSection,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Get all pages marked for docs section navigation
|
||||
// Used by DocsSidebar to build the left navigation
|
||||
export const getDocsPages = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("pages"),
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
docsSectionGroupOrder: v.optional(v.number()),
|
||||
}),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const pages = await ctx.db
|
||||
.query("pages")
|
||||
.withIndex("by_docsSection", (q) => q.eq("docsSection", true))
|
||||
.collect();
|
||||
|
||||
// Filter to only published pages
|
||||
const publishedDocs = pages.filter((p) => p.published);
|
||||
|
||||
// Sort by docsSectionOrder, then by title
|
||||
const sortedDocs = publishedDocs.sort((a, b) => {
|
||||
const orderA = a.docsSectionOrder ?? 999;
|
||||
const orderB = b.docsSectionOrder ?? 999;
|
||||
if (orderA !== orderB) return orderA - orderB;
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
|
||||
return sortedDocs.map((page) => ({
|
||||
_id: page._id,
|
||||
slug: page.slug,
|
||||
title: page.title,
|
||||
docsSectionGroup: page.docsSectionGroup,
|
||||
docsSectionOrder: page.docsSectionOrder,
|
||||
docsSectionGroupOrder: page.docsSectionGroupOrder,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
// Get the docs landing page (page with docsLanding: true)
|
||||
// Returns null if no landing page is set
|
||||
export const getDocsLandingPage = query({
|
||||
args: {},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
_id: v.id("pages"),
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
content: v.string(),
|
||||
image: v.optional(v.string()),
|
||||
showImageAtTop: v.optional(v.boolean()),
|
||||
authorName: v.optional(v.string()),
|
||||
authorImage: v.optional(v.string()),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
// Get all docs pages and find one with docsLanding: true
|
||||
const pages = await ctx.db
|
||||
.query("pages")
|
||||
.withIndex("by_docsSection", (q) => q.eq("docsSection", true))
|
||||
.collect();
|
||||
|
||||
const landing = pages.find((p) => p.published && p.docsLanding);
|
||||
|
||||
if (!landing) return null;
|
||||
|
||||
return {
|
||||
_id: landing._id,
|
||||
slug: landing.slug,
|
||||
title: landing.title,
|
||||
content: landing.content,
|
||||
image: landing.image,
|
||||
showImageAtTop: landing.showImageAtTop,
|
||||
authorName: landing.authorName,
|
||||
authorImage: landing.authorImage,
|
||||
docsSectionGroup: landing.docsSectionGroup,
|
||||
docsSectionOrder: landing.docsSectionOrder,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -252,6 +341,11 @@ export const syncPagesPublic = mutation({
|
||||
contactForm: v.optional(v.boolean()),
|
||||
newsletter: v.optional(v.boolean()),
|
||||
textAlign: v.optional(v.string()),
|
||||
docsSection: v.optional(v.boolean()),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
docsSectionGroupOrder: v.optional(v.number()),
|
||||
docsLanding: v.optional(v.boolean()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -300,6 +394,11 @@ export const syncPagesPublic = mutation({
|
||||
contactForm: page.contactForm,
|
||||
newsletter: page.newsletter,
|
||||
textAlign: page.textAlign,
|
||||
docsSection: page.docsSection,
|
||||
docsSectionGroup: page.docsSectionGroup,
|
||||
docsSectionOrder: page.docsSectionOrder,
|
||||
docsSectionGroupOrder: page.docsSectionGroupOrder,
|
||||
docsLanding: page.docsLanding,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
|
||||
117
convex/posts.ts
117
convex/posts.ts
@@ -238,6 +238,7 @@ export const getPostBySlug = query({
|
||||
aiChat: v.optional(v.boolean()),
|
||||
newsletter: v.optional(v.boolean()),
|
||||
contactForm: v.optional(v.boolean()),
|
||||
docsSection: v.optional(v.boolean()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
@@ -277,6 +278,7 @@ export const getPostBySlug = query({
|
||||
aiChat: post.aiChat,
|
||||
newsletter: post.newsletter,
|
||||
contactForm: post.contactForm,
|
||||
docsSection: post.docsSection,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -384,6 +386,11 @@ export const syncPosts = internalMutation({
|
||||
newsletter: v.optional(v.boolean()),
|
||||
contactForm: v.optional(v.boolean()),
|
||||
unlisted: v.optional(v.boolean()),
|
||||
docsSection: v.optional(v.boolean()),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
docsSectionGroupOrder: v.optional(v.number()),
|
||||
docsLanding: v.optional(v.boolean()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -435,6 +442,11 @@ export const syncPosts = internalMutation({
|
||||
newsletter: post.newsletter,
|
||||
contactForm: post.contactForm,
|
||||
unlisted: post.unlisted,
|
||||
docsSection: post.docsSection,
|
||||
docsSectionGroup: post.docsSectionGroup,
|
||||
docsSectionOrder: post.docsSectionOrder,
|
||||
docsSectionGroupOrder: post.docsSectionGroupOrder,
|
||||
docsLanding: post.docsLanding,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
@@ -490,6 +502,11 @@ export const syncPostsPublic = mutation({
|
||||
newsletter: v.optional(v.boolean()),
|
||||
contactForm: v.optional(v.boolean()),
|
||||
unlisted: v.optional(v.boolean()),
|
||||
docsSection: v.optional(v.boolean()),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
docsSectionGroupOrder: v.optional(v.number()),
|
||||
docsLanding: v.optional(v.boolean()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -541,6 +558,11 @@ export const syncPostsPublic = mutation({
|
||||
newsletter: post.newsletter,
|
||||
contactForm: post.contactForm,
|
||||
unlisted: post.unlisted,
|
||||
docsSection: post.docsSection,
|
||||
docsSectionGroup: post.docsSectionGroup,
|
||||
docsSectionOrder: post.docsSectionOrder,
|
||||
docsSectionGroupOrder: post.docsSectionGroupOrder,
|
||||
docsLanding: post.docsLanding,
|
||||
lastSyncedAt: now,
|
||||
});
|
||||
updated++;
|
||||
@@ -874,3 +896,98 @@ export const getPostsByAuthor = query({
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
// Get all posts marked for docs section navigation
|
||||
// Used by DocsSidebar to build the left navigation
|
||||
export const getDocsPosts = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("posts"),
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
docsSectionGroupOrder: v.optional(v.number()),
|
||||
}),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const posts = await ctx.db
|
||||
.query("posts")
|
||||
.withIndex("by_docsSection", (q) => q.eq("docsSection", true))
|
||||
.collect();
|
||||
|
||||
// Filter to only published posts
|
||||
const publishedDocs = posts.filter((p) => p.published);
|
||||
|
||||
// Sort by docsSectionOrder, then by title
|
||||
const sortedDocs = publishedDocs.sort((a, b) => {
|
||||
const orderA = a.docsSectionOrder ?? 999;
|
||||
const orderB = b.docsSectionOrder ?? 999;
|
||||
if (orderA !== orderB) return orderA - orderB;
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
|
||||
return sortedDocs.map((post) => ({
|
||||
_id: post._id,
|
||||
slug: post.slug,
|
||||
title: post.title,
|
||||
docsSectionGroup: post.docsSectionGroup,
|
||||
docsSectionOrder: post.docsSectionOrder,
|
||||
docsSectionGroupOrder: post.docsSectionGroupOrder,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
// Get the docs landing page (post with docsLanding: true)
|
||||
// Returns null if no landing page is set
|
||||
export const getDocsLandingPost = query({
|
||||
args: {},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
_id: v.id("posts"),
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
description: v.string(),
|
||||
content: v.string(),
|
||||
date: v.string(),
|
||||
tags: v.array(v.string()),
|
||||
readTime: v.optional(v.string()),
|
||||
image: v.optional(v.string()),
|
||||
showImageAtTop: v.optional(v.boolean()),
|
||||
authorName: v.optional(v.string()),
|
||||
authorImage: v.optional(v.string()),
|
||||
docsSectionGroup: v.optional(v.string()),
|
||||
docsSectionOrder: v.optional(v.number()),
|
||||
}),
|
||||
v.null(),
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
// Get all docs posts and find one with docsLanding: true
|
||||
const posts = await ctx.db
|
||||
.query("posts")
|
||||
.withIndex("by_docsSection", (q) => q.eq("docsSection", true))
|
||||
.collect();
|
||||
|
||||
const landing = posts.find((p) => p.published && p.docsLanding);
|
||||
|
||||
if (!landing) return null;
|
||||
|
||||
return {
|
||||
_id: landing._id,
|
||||
slug: landing.slug,
|
||||
title: landing.title,
|
||||
description: landing.description,
|
||||
content: landing.content,
|
||||
date: landing.date,
|
||||
tags: landing.tags,
|
||||
readTime: landing.readTime,
|
||||
image: landing.image,
|
||||
showImageAtTop: landing.showImageAtTop,
|
||||
authorName: landing.authorName,
|
||||
authorImage: landing.authorImage,
|
||||
docsSectionGroup: landing.docsSectionGroup,
|
||||
docsSectionOrder: landing.docsSectionOrder,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,6 +29,11 @@ export default defineSchema({
|
||||
newsletter: v.optional(v.boolean()), // Override newsletter signup display (true/false)
|
||||
contactForm: v.optional(v.boolean()), // Enable contact form on this post
|
||||
unlisted: v.optional(v.boolean()), // Hide from listings but allow direct access via slug
|
||||
docsSection: v.optional(v.boolean()), // Include in docs navigation
|
||||
docsSectionGroup: v.optional(v.string()), // Sidebar group name in docs
|
||||
docsSectionOrder: v.optional(v.number()), // Order within group (lower = first)
|
||||
docsSectionGroupOrder: v.optional(v.number()), // Order of group itself (lower = first)
|
||||
docsLanding: v.optional(v.boolean()), // Use as /docs landing page
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
@@ -37,6 +42,7 @@ export default defineSchema({
|
||||
.index("by_featured", ["featured"])
|
||||
.index("by_blogFeatured", ["blogFeatured"])
|
||||
.index("by_authorName", ["authorName"])
|
||||
.index("by_docsSection", ["docsSection"])
|
||||
.searchIndex("search_content", {
|
||||
searchField: "content",
|
||||
filterFields: ["published"],
|
||||
@@ -70,11 +76,17 @@ export default defineSchema({
|
||||
contactForm: v.optional(v.boolean()), // Enable contact form on this page
|
||||
newsletter: v.optional(v.boolean()), // Override newsletter signup display (true/false)
|
||||
textAlign: v.optional(v.string()), // Text alignment: "left", "center", "right" (default: "left")
|
||||
docsSection: v.optional(v.boolean()), // Include in docs navigation
|
||||
docsSectionGroup: v.optional(v.string()), // Sidebar group name in docs
|
||||
docsSectionOrder: v.optional(v.number()), // Order within group (lower = first)
|
||||
docsSectionGroupOrder: v.optional(v.number()), // Order of group itself (lower = first)
|
||||
docsLanding: v.optional(v.boolean()), // Use as /docs landing page
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
.index("by_published", ["published"])
|
||||
.index("by_featured", ["featured"])
|
||||
.index("by_docsSection", ["docsSection"])
|
||||
.searchIndex("search_content", {
|
||||
searchField: "content",
|
||||
filterFields: ["published"],
|
||||
|
||||
Reference in New Issue
Block a user