UNPKG

astro-loader-hashnode

Version:

Astro content loader for seamlessly integrating Hashnode blog posts into your Astro website using the Content Layer API

151 lines (150 loc) 5.03 kB
/** * Search Loader - Handles Hashnode search functionality */ import { BaseHashnodeLoader, paginateResults, flattenPaginatedResults, } from './base.js'; import { searchResultSchema } from '../types/schema.js'; /** * Transform search result to Astro content format */ function transformSearchResult(post, searchTerm) { return { // Core content id: post.id, title: post.title, brief: post.brief || '', slug: post.slug, url: post.url, // Search metadata searchTerm, searchRelevance: calculateRelevance(post, searchTerm), // Publication date publishedAt: post.publishedAt ? new Date(post.publishedAt) : new Date(), // Stats reactionCount: post.reactionCount || 0, views: post.views || 0, // Author information author: { id: post.author?.id || '', name: post.author?.name || '', username: post.author?.username || '', profilePicture: post.author?.profilePicture || '', }, // Cover image coverImage: post.coverImage ? { url: post.coverImage.url, } : undefined, // Publication info publication: post.publication ? { title: post.publication.title, url: post.publication.url, } : undefined, // Raw data for advanced use cases raw: { cuid: post.cuid, }, }; } /** * Calculate search relevance score (simple implementation) */ function calculateRelevance(post, searchTerm) { const term = searchTerm.toLowerCase(); let score = 0; // Title match gets highest score if (post.title.toLowerCase().includes(term)) { score += 10; } // Brief/description match if (post.brief?.toLowerCase().includes(term)) { score += 5; } // Reaction count and views contribute to relevance score += Math.min((post.reactionCount || 0) / 10, 3); score += Math.min((post.views || 0) / 1000, 2); return Math.round(score * 10) / 10; // Round to 1 decimal place } /** * Search Loader Class */ export class SearchLoader extends BaseHashnodeLoader { options; constructor(options) { super({ ...options, collection: 'search', schema: searchResultSchema, }); this.options = options; } /** * Fetch search results from Hashnode API */ async fetchData() { const { searchTerms, maxResults = 50 } = this.options; if (!searchTerms || searchTerms.length === 0) { return []; } const allResults = []; // Search for each term for (const searchTerm of searchTerms) { try { const paginatedResults = paginateResults(async (cursor) => { const result = await this.client.searchPosts(searchTerm, { first: 20, after: cursor, }); return { items: result.searchPostsOfPublication.edges.map(edge => ({ post: edge.node, searchTerm, })), pageInfo: result.searchPostsOfPublication.pageInfo, }; }, Math.min(maxResults, 100) // Limit per search term ); const results = await flattenPaginatedResults(paginatedResults); allResults.push(...results); } catch { // Silently continue with other search terms to avoid breaking the entire search // In a production environment, you might want to use a proper logging service // instead of console.warn } } // Remove duplicates and sort by relevance const uniqueResults = Array.from(new Map(allResults.map(result => [result.post.id, result])).values()).sort((a, b) => { const relevanceA = calculateRelevance(a.post, a.searchTerm); const relevanceB = calculateRelevance(b.post, b.searchTerm); return relevanceB - relevanceA; // Descending order }); return maxResults ? uniqueResults.slice(0, maxResults) : uniqueResults; } /** * Transform search result to Astro content format */ transformItem(result) { return transformSearchResult(result.post, result.searchTerm); } /** * Generate ID for search result */ generateId(result) { return `${result.searchTerm}-${result.post.slug || result.post.cuid || result.post.id}`; } } /** * Create a search loader */ export function createSearchLoader(options) { return new SearchLoader(options); } /** * Create an Astro Loader for search */ export function searchLoader(options) { return createSearchLoader(options).createLoader(); }