Ctrl K
ComunidadGuías…

docs/src/lib/blog.ts

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { blogSource } from "@/lib/blog-source";
import { createOGImageUrl } from "@/lib/metadata";

export type BlogPost = ReturnType<typeof blogSource.getPages>[number];

const WORDS_PER_MINUTE = 220;

/** Reading time in whole minutes, derived from the indexed body text. */
export function readingTimeMinutes(post: BlogPost): number {
  const { structuredData } = post.data;
  const text = [
    ...structuredData.headings.map(h => h.content),
    ...structuredData.contents.map(c => c.content),
  ].join(" ");
  const words = text.split(/\s+/).filter(Boolean).length;
  return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
}

export function wordCount(post: BlogPost): number {
  const { structuredData } = post.data;
  return structuredData.contents.map(c => c.content).join(" ").split(/\s+/).filter(Boolean).length;
}

/**
 * Cover image for a post. Falls back to the generated OG card so every post
 * has a thumbnail on the index and in schema even before a real cover exists.
 */
export function postImageUrl(post: BlogPost): string {
  return post.data.image || createOGImageUrl(post.data.title, post.data.description, "Blog").url;
}

export function isGeneratedImage(url: string): boolean {
  return url.startsWith("/og/");
}

export function absoluteUrl(path: string): string {
  return path.startsWith("http") ? path : `https://rybbit.com${path}`;
}

/** Newest first. */
export function sortedPosts(): BlogPost[] {
  return [...blogSource.getPages()].sort(
    (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(),
  );
}

/** Posts sharing the most tags with `current`, newest first as a tie-break. */
export function relatedPosts(current: BlogPost, limit = 3): BlogPost[] {
  const tags = new Set(current.data.tags ?? []);
  return sortedPosts()
    .filter(post => post.url !== current.url)
    .map((post, index) => ({
      post,
      shared: (post.data.tags ?? []).filter(tag => tags.has(tag)).length,
      index,
    }))
    .sort((a, b) => b.shared - a.shared || a.index - b.index)
    .slice(0, limit)
    .map(entry => entry.post);
}