diff --git a/app/components/ContentThumbnailTemplate.js b/app/components/ContentThumbnailTemplate.js index 0892b99..1305f6d 100644 --- a/app/components/ContentThumbnailTemplate.js +++ b/app/components/ContentThumbnailTemplate.js @@ -10,9 +10,8 @@ import ContentContainer from "./ContentContainer"; */ const ContentThumbnailTemplate = ({ post, - variant = "vertical", className = "", - showReadingTime = true, + variant = "vertical", // Internal prop for testing/development }) => { // Post-specific background selection - different SVG for each post const getBackgroundImage = (slug, variant) => { diff --git a/app/test-thumbnail/page.js b/app/test-thumbnail/page.js index 6b2d4b3..56a96e2 100644 --- a/app/test-thumbnail/page.js +++ b/app/test-thumbnail/page.js @@ -9,10 +9,7 @@ const mockPost1 = { "Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals", author: "Author name", date: "2025-04-15", - tags: ["conflict-resolution", "governance", "community"], }, - wordCount: 467, - readingTime: 3, }; const mockPost2 = { @@ -23,10 +20,7 @@ const mockPost2 = { "Tactics to protect members, secure communication, and prevent Infiltration", author: "Author name", date: "2025-04-10", - tags: ["community-building", "sustainability", "structure"], }, - wordCount: 523, - readingTime: 4, }; const mockPost3 = { @@ -37,10 +31,7 @@ const mockPost3 = { "A brief guide to collaborative nonhierarchical decision making", author: "Author name", date: "2025-04-05", - tags: ["communication", "remote-work", "collaboration"], }, - wordCount: 389, - readingTime: 2, }; export default function TestThumbnailPage() { @@ -58,23 +49,9 @@ export default function TestThumbnailPage() { Vertical Variant
- - - + + +
@@ -85,16 +62,8 @@ export default function TestThumbnailPage() {
- - + +
@@ -119,18 +88,12 @@ export default function TestThumbnailPage() { Optional Props: diff --git a/lib/contentProcessor.js b/lib/contentProcessor.js index 5801d6b..f7138e2 100644 --- a/lib/contentProcessor.js +++ b/lib/contentProcessor.js @@ -108,8 +108,6 @@ class ContentProcessor { frontmatter: processFrontmatter(sanitizedFrontmatter), content: processedContent.content, htmlContent: processedContent.htmlContent, - wordCount: processedContent.wordCount, - readingTime: processedContent.readingTime, headings: processedContent.headings, links: processedContent.links, images: processedContent.images, @@ -195,46 +193,6 @@ class ContentProcessor { return recentPosts; } - /** - * Get blog posts by tag - * @param {string} tag - The tag to filter by - * @returns {Array} Array of blog post objects matching the tag - */ - getPostsByTag(tag) { - const cacheKey = `tag:${tag}`; - const cached = getCachedBlogList(cacheKey); - if (cached) return cached; - - const allPosts = this.getAllPosts(); - const taggedPosts = allPosts.filter( - (post) => post.frontmatter.tags && post.frontmatter.tags.includes(tag) - ); - - cacheBlogList(cacheKey, taggedPosts); - return taggedPosts; - } - - /** - * Get all unique tags with caching - * @returns {Array} Array of unique tags - */ - getAllTags() { - const cached = getCachedTags(); - if (cached) return cached; - - const allPosts = this.getAllPosts(); - const tags = new Set(); - allPosts.forEach((post) => { - if (post.frontmatter.tags) { - post.frontmatter.tags.forEach((tag) => tags.add(tag)); - } - }); - - const tagsArray = Array.from(tags).sort(); - cacheTags(tagsArray); - return tagsArray; - } - /** * Search blog posts * @param {string} query - Search query @@ -255,11 +213,8 @@ class ContentProcessor { .toLowerCase() .includes(searchTerm); const contentMatch = post.content.toLowerCase().includes(searchTerm); - const tagMatch = post.frontmatter.tags?.some((tag) => - tag.toLowerCase().includes(searchTerm) - ); - return titleMatch || descriptionMatch || contentMatch || tagMatch; + return titleMatch || descriptionMatch || contentMatch; }); return results.slice(0, limit); @@ -271,22 +226,12 @@ class ContentProcessor { */ getBlogStats() { const allPosts = this.getAllPosts(); - const tags = this.getAllTags(); return { totalPosts: allPosts.length, - totalTags: tags.length, totalAuthors: new Set( allPosts.map((post) => post.frontmatter.author).size ), - totalWords: allPosts.reduce((sum, post) => sum + post.wordCount, 0), - averageReadingTime: - allPosts.length > 0 - ? Math.round( - allPosts.reduce((sum, post) => sum + post.readingTime, 0) / - allPosts.length - ) - : 0, dateRange: { earliest: allPosts.length > 0 @@ -367,8 +312,6 @@ export const getAllPosts = () => contentProcessor.getAllPosts(); export const getBlogPostBySlug = (slug) => contentProcessor.getBlogPostBySlug(slug); export const getRecentPosts = (limit) => contentProcessor.getRecentPosts(limit); -export const getPostsByTag = (tag) => contentProcessor.getPostsByTag(tag); -export const getAllTags = () => contentProcessor.getAllTags(); export const searchPosts = (query, limit) => contentProcessor.searchPosts(query, limit); export const getBlogStats = () => contentProcessor.getBlogStats(); diff --git a/lib/mdx.js b/lib/mdx.js index 9049e26..190cb15 100644 --- a/lib/mdx.js +++ b/lib/mdx.js @@ -24,8 +24,6 @@ export function processMarkdown(markdown) { return { content: "", htmlContent: "", - wordCount: 0, - readingTime: 0, headings: [], links: [], images: [], @@ -41,18 +39,12 @@ export function processMarkdown(markdown) { // Extract images const images = extractImages(markdown); - // Calculate word count and reading time - const wordCount = calculateWordCount(markdown); - const readingTime = calculateReadingTime(wordCount); - // Convert markdown to HTML const htmlContent = markdownToHtml(markdown); return { content: markdown, htmlContent, - wordCount, - readingTime, headings, links, images, @@ -141,31 +133,6 @@ function generateHeadingId(text) { .trim(); } -/** - * Calculate word count from markdown content - * @param {string} markdown - Raw markdown content - * @returns {number} Word count - */ -function calculateWordCount(markdown) { - // Remove markdown syntax and count words - const cleanText = markdown - .replace(/[#*`~\[\]()]/g, "") // Remove markdown characters - .replace(/\n+/g, " ") // Replace newlines with spaces - .trim(); - - return cleanText.split(/\s+/).filter((word) => word.length > 0).length; -} - -/** - * Calculate estimated reading time - * @param {number} wordCount - Number of words - * @returns {number} Reading time in minutes - */ -function calculateReadingTime(wordCount) { - const wordsPerMinute = 200; // Average reading speed - return Math.ceil(wordCount / wordsPerMinute); -} - /** * Convert markdown to HTML with enhanced formatting * @param {string} markdown - Raw markdown content @@ -255,9 +222,6 @@ export function processFrontmatter(frontmatter) { month: new Date(frontmatter.date).getMonth() + 1, day: new Date(frontmatter.date).getDate(), isRecent: isRecentPost(frontmatter.date), - readingTime: frontmatter.content - ? calculateReadingTime(calculateWordCount(frontmatter.content)) - : 0, }; return processed; diff --git a/lib/validation.js b/lib/validation.js index 379a3d8..e1fde28 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -29,16 +29,6 @@ export const BLOG_POST_SCHEMA = { required: true, pattern: /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD format }, - tags: { - type: "array", - required: false, - default: [], - items: { - type: "string", - minLength: 1, - maxLength: 20, - }, - }, related: { type: "array", required: false, diff --git a/tests/unit/ContentThumbnailTemplate.test.jsx b/tests/unit/ContentThumbnailTemplate.test.jsx index 3bc4327..dab8a96 100644 --- a/tests/unit/ContentThumbnailTemplate.test.jsx +++ b/tests/unit/ContentThumbnailTemplate.test.jsx @@ -28,17 +28,14 @@ const mockPost = { "This is a test description for the blog post that should be long enough to test truncation.", author: "Test Author", date: "2025-04-15", - tags: ["test", "blog", "example"], backgroundImages: ["/test-image-1.jpg", "/test-image-2.jpg"], }, - wordCount: 500, - readingTime: 3, }; describe("ContentThumbnailTemplate", () => { describe("Vertical Variant", () => { it("should render vertical variant with correct dimensions", () => { - render(); + render(); const container = screen.getByRole("link"); expect(container).toBeInTheDocument(); @@ -49,7 +46,7 @@ describe("ContentThumbnailTemplate", () => { }); it("should display post title and description", () => { - render(); + render(); expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); expect( @@ -58,57 +55,21 @@ describe("ContentThumbnailTemplate", () => { }); it("should display tags when showTags is true", () => { - render( - - ); + render(); expect(screen.getByText("test")).toBeInTheDocument(); expect(screen.getByText("blog")).toBeInTheDocument(); }); it("should hide tags when showTags is false", () => { - render( - - ); + render(); expect(screen.queryByText("test")).not.toBeInTheDocument(); expect(screen.queryByText("blog")).not.toBeInTheDocument(); }); - it("should display reading time when showReadingTime is true", () => { - render( - - ); - - expect(screen.getByText(/3 min read/)).toBeInTheDocument(); - }); - - it("should hide reading time when showReadingTime is false", () => { - render( - - ); - - expect(screen.queryByText(/min read/)).not.toBeInTheDocument(); - }); - it("should display author and date", () => { - render(); + render(); expect(screen.getByText("Test Author")).toBeInTheDocument(); // Check for "Month Year" format (e.g., "April 2025") @@ -118,7 +79,7 @@ describe("ContentThumbnailTemplate", () => { describe("Horizontal Variant", () => { it("should render horizontal variant", () => { - render(); + render(); const container = screen.getByRole("link"); expect(container).toBeInTheDocument(); @@ -129,7 +90,7 @@ describe("ContentThumbnailTemplate", () => { }); it("should display post information in horizontal layout", () => { - render(); + render(); expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); expect( @@ -142,11 +103,7 @@ describe("ContentThumbnailTemplate", () => { describe("Props and Customization", () => { it("should apply custom className", () => { render( - + ); const container = screen.getByRole("link"); @@ -154,7 +111,7 @@ describe("ContentThumbnailTemplate", () => { }); it("should generate correct link href", () => { - render(); + render(); const link = screen.getByRole("link"); expect(link).toHaveAttribute("href", "/blog/test-post"); @@ -169,9 +126,7 @@ describe("ContentThumbnailTemplate", () => { }, }; - render( - - ); + render(); // Should still render without errors expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); @@ -186,9 +141,7 @@ describe("ContentThumbnailTemplate", () => { }, }; - render( - - ); + render(); // Should still render without errors expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); @@ -208,11 +161,5 @@ describe("ContentThumbnailTemplate", () => { expect(screen.getByText("test")).toBeInTheDocument(); }); - - it("should show reading time by default", () => { - render(); - - expect(screen.getByText(/min read/)).toBeInTheDocument(); - }); }); }); diff --git a/tests/unit/contentProcessor.test.js b/tests/unit/contentProcessor.test.js index 96e5c90..46a2608 100644 --- a/tests/unit/contentProcessor.test.js +++ b/tests/unit/contentProcessor.test.js @@ -3,7 +3,6 @@ import { contentProcessor, getAllPosts, getBlogStats, - getAllTags, } from "../../lib/contentProcessor.js"; describe("Content Processor", () => { @@ -25,14 +24,6 @@ describe("Content Processor", () => { it("should extract blog statistics", () => { const stats = getBlogStats(); expect(stats.totalPosts).toBeGreaterThan(0); - expect(stats.totalTags).toBeGreaterThan(0); - expect(stats.totalWords).toBeGreaterThan(0); - }); - - it("should extract tags from posts", () => { - const tags = getAllTags(); - expect(Array.isArray(tags)).toBe(true); - expect(tags.length).toBeGreaterThan(0); }); }); @@ -44,8 +35,6 @@ describe("Content Processor", () => { expect(firstPost).toHaveProperty("frontmatter"); expect(firstPost).toHaveProperty("content"); expect(firstPost).toHaveProperty("htmlContent"); - expect(firstPost).toHaveProperty("wordCount"); - expect(firstPost).toHaveProperty("readingTime"); expect(firstPost).toHaveProperty("headings"); expect(firstPost).toHaveProperty("tableOfContents"); }); @@ -58,16 +47,6 @@ describe("Content Processor", () => { expect(typeof firstPost.slug).toBe("string"); expect(firstPost.slug.length).toBeGreaterThan(0); }); - - it("should calculate word count and reading time", () => { - const posts = getAllPosts(); - const firstPost = posts[0]; - - expect(firstPost.wordCount).toBeGreaterThan(0); - expect(firstPost.readingTime).toBeGreaterThan(0); - expect(typeof firstPost.wordCount).toBe("number"); - expect(typeof firstPost.readingTime).toBe("number"); - }); }); describe("Content Enhancement", () => { diff --git a/tests/unit/validation.test.js b/tests/unit/validation.test.js index a4a2527..15ff950 100644 --- a/tests/unit/validation.test.js +++ b/tests/unit/validation.test.js @@ -14,7 +14,6 @@ describe("Blog Post Validation", () => { "This is a test description that meets the minimum length requirement", author: "Test Author", date: "2025-04-15", - tags: ["test", "blog"], related: ["post-1", "post-2"], }; @@ -27,7 +26,6 @@ describe("Blog Post Validation", () => { const invalidPost = { title: "Test Title", // Missing description, author, date - tags: ["test"], }; const result = validateBlogPost(invalidPost); @@ -64,41 +62,6 @@ describe("Blog Post Validation", () => { expect(result.isValid).toBe(false); expect(result.errors).toContain("Field date format is invalid"); }); - - it("should validate tags array", () => { - const invalidTags = { - title: "Test Title", - description: - "This is a test description that meets the minimum length requirement", - author: "Test Author", - date: "2025-04-15", - tags: "not-an-array", // Should be array - }; - - const result = validateBlogPost(invalidTags); - expect(result.isValid).toBe(false); - expect(result.errors).toContain("Field tags must be an array"); - }); - - it("should validate tag item lengths", () => { - const invalidTagItems = { - title: "Test Title", - description: - "This is a test description that meets the minimum length requirement", - author: "Test Author", - date: "2025-04-15", - tags: ["", "very-long-tag-name-that-exceeds-maximum-length"], - }; - - const result = validateBlogPost(invalidTagItems); - expect(result.isValid).toBe(false); - expect(result.errors).toContain( - "Item 0 in tags must be at least 1 characters" - ); - expect(result.errors).toContain( - "Item 1 in tags must be no more than 20 characters" - ); - }); }); describe("sanitizeBlogPost", () => { @@ -108,7 +71,6 @@ describe("Blog Post Validation", () => { description: "Test description", author: "Test Author", date: "2025-04-15", - tags: ["test"], related: ["post-1"], }; @@ -122,11 +84,10 @@ describe("Blog Post Validation", () => { description: "Test description", author: "Test Author", date: "2025-04-15", - // Missing tags and related + // Missing related }; const sanitized = sanitizeBlogPost(post); - expect(sanitized.tags).toEqual([]); expect(sanitized.related).toEqual([]); }); @@ -136,12 +97,10 @@ describe("Blog Post Validation", () => { description: "Test description", author: "Test Author", date: "2025-04-15", - tags: ["custom-tag"], related: ["custom-post"], }; const sanitized = sanitizeBlogPost(post); - expect(sanitized.tags).toEqual(["custom-tag"]); expect(sanitized.related).toEqual(["custom-post"]); }); }); @@ -152,7 +111,6 @@ describe("Blog Post Validation", () => { expect(BLOG_POST_SCHEMA).toHaveProperty("description"); expect(BLOG_POST_SCHEMA).toHaveProperty("author"); expect(BLOG_POST_SCHEMA).toHaveProperty("date"); - expect(BLOG_POST_SCHEMA).toHaveProperty("tags"); expect(BLOG_POST_SCHEMA).toHaveProperty("related"); }); @@ -161,7 +119,6 @@ describe("Blog Post Validation", () => { expect(BLOG_POST_SCHEMA.description.required).toBe(true); expect(BLOG_POST_SCHEMA.author.required).toBe(true); expect(BLOG_POST_SCHEMA.date.required).toBe(true); - expect(BLOG_POST_SCHEMA.tags.required).toBe(false); expect(BLOG_POST_SCHEMA.related.required).toBe(false); }); });