diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index ffc17fa..afca897 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -63,13 +63,15 @@ const graphData = { edges: [] }; -// Create edges between posts and their tags +// Create edges between posts and their tags, and between related posts +const processedPostPairs = new Set(); // Avoid duplicate A->B and B->A edges + sortedPosts .filter(post => !post.data.draft) .forEach(post => { const postTags = post.data.tags || []; - // Add edges from post to tags + // 1. Add edges from post to tags postTags.forEach(tag => { graphData.edges.push({ source: post.slug, @@ -78,22 +80,60 @@ sortedPosts strength: 1 }); }); + + // --- Find Related Posts (Explicit + Implicit) --- + const MAX_RELATED_LINKS = 3; + const MIN_SHARED_TAGS_FOR_LINK = 2; - // Check if post references other posts (optional) - // This requires a related_posts field in frontmatter - if (post.data.related_posts && Array.isArray(post.data.related_posts)) { - post.data.related_posts.forEach(relatedSlug => { - // Make sure related post exists - if (sortedPosts.some(p => p.slug === relatedSlug)) { - graphData.edges.push({ - source: post.slug, - target: relatedSlug, - type: 'post-post', - strength: 2 - }); + let relatedLinks = new Map(); // Use Map to store related slug and relation type (explicit/implicit) + + // a. Find explicitly related posts + const explicitSlugs = new Set(post.data.related_posts || []); + explicitSlugs.forEach(slug => { + if (sortedPosts.some(p => p.slug === slug && !p.data.draft)) { + relatedLinks.set(slug, 'explicit'); + } + }); + + // b. Find implicitly related posts by tag similarity (if needed) + if (relatedLinks.size < MAX_RELATED_LINKS && postTags.length > 0) { + const potentialRelated = sortedPosts + .filter(otherPost => + !otherPost.data.draft && + otherPost.slug !== post.slug && // Not the same post + !relatedLinks.has(otherPost.slug) // Not already added + ) + .map(otherPost => { + const otherTags = otherPost.data.tags || []; + const sharedTagsCount = postTags.filter(tag => otherTags.includes(tag)).length; + return { slug: otherPost.slug, score: sharedTagsCount }; + }) + .filter(item => item.score >= MIN_SHARED_TAGS_FOR_LINK) + .sort((a, b) => b.score - a.score); // Sort by most shared tags + + potentialRelated.forEach(item => { + if (relatedLinks.size < MAX_RELATED_LINKS) { + relatedLinks.set(item.slug, 'implicit'); } }); } + + // 2. Create edges for the found related posts + relatedLinks.forEach((relationType, relatedSlug) => { + const pairKey1 = `${post.slug}-${relatedSlug}`; + const pairKey2 = `${relatedSlug}-${post.slug}`; + + if (!processedPostPairs.has(pairKey1) && !processedPostPairs.has(pairKey2)) { + graphData.edges.push({ + source: post.slug, + target: relatedSlug, + type: 'post-post', + strength: relationType === 'explicit' ? 3 : 1.5 // Stronger edge for explicit links + }); + processedPostPairs.add(pairKey1); + processedPostPairs.add(pairKey2); // Add both directions to set + } + }); }); // Terminal commands for tech effect