feat: implement NewsList component and configure TinaCMS news and documentary collections

This commit is contained in:
Esteban 2026-05-05 00:21:23 -05:00
parent 70d963770e
commit 7598808f60
12 changed files with 100 additions and 54 deletions

View File

@ -1,85 +1,111 @@
--- ---
import { Image } from "astro:assets" import { Image } from "astro:assets";
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import "dayjs/locale/es"; import "dayjs/locale/es";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
const regionNames = new Intl.DisplayNames(['es'], { type: 'region' }); const regionNames = new Intl.DisplayNames(["es"], { type: "region" });
const locale = Astro.currentLocale; const locale = Astro.currentLocale;
import { createTranslator } from '@/i18n'; import { createTranslator } from "@/i18n";
const tl = createTranslator(Astro.currentLocale); const tl = createTranslator(Astro.currentLocale);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.locale(locale); dayjs.locale(locale);
const { data, content } = Astro.props; const { data, content } = Astro.props;
const nicedate = dayjs.utc(data.data.date).format("D MMMM YYYY"); const nicedate = dayjs.utc(data.data.date).format("D MMMM YYYY");
const countryName = data?.data?.country ? regionNames.of(data.data.country) : ""; const countryName = data?.data?.country
? regionNames.of(data.data.country)
: "";
const locationArray = [data.data.city,countryName] const locationArray = [data.data.city, countryName];
const location = locationArray.filter(Boolean).join(', '); const location = locationArray.filter(Boolean).join(", ");
const newsUrl = `/${locale}/news/${data.id}`; const newsUrl = `/${locale}/news/${data.id}`;
const rawContent = content?.body || ""; const rawContent = content?.body || "";
const plainText = rawContent const plainText = rawContent
.replace(/^#.*$/gm, '') .replace(/^#.*$/gm, "")
.replace(/^###.*$/gm, '') .replace(/^###.*$/gm, "")
.replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/\*([^*]+)\*/g, '$1') .replace(/\*([^*]+)\*/g, "$1")
.replace(/_([^_]+)_/g, '$1') .replace(/_([^_]+)_/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/^>.*$/gm, '') .replace(/^>.*$/gm, "")
.replace(/`[^`]+`/g, '') .replace(/`[^`]+`/g, "")
.replace(/^[-*]\s+/gm, '') .replace(/^[-*]\s+/gm, "")
.trim(); .trim();
const words = plainText.split(/\s+/).filter(w => w.length > 0).slice(0, 40); const words = plainText
const excerpt = words.join(' ') + (words.length === 40 ? '...' : ''); .split(/\s+/)
.filter((w) => w.length > 0)
.slice(0, 40);
const excerpt = words.join(" ") + (words.length === 40 ? "..." : "");
--- ---
<a href={newsUrl} class="block group"> <a href={newsUrl} class="block group">
<article class="flex flex-col md:flex-row gap-6 md:gap-8 p-4 md:p-6 border-b border-tertiary/20 hover:bg-tertiary/5 transition-colors"> <article
class="flex flex-col md:flex-row gap-6 md:gap-8 p-4 md:p-6 border-b border-tertiary/20 hover:bg-tertiary/5 transition-colors"
>
<div class="md:w-1/3 flex-shrink-0 overflow-hidden"> <div class="md:w-1/3 flex-shrink-0 overflow-hidden">
<Image <Image
src={data.data.thumbnail} src={data.data.thumbnail_square || data.data.thumbnail}
alt={data.data.title} alt={data.data.title}
width={400} width={480}
height={300} height={340}
class="w-full h-48 md:h-full object-cover transform group-hover:scale-105 transition-transform duration-300" class="w-full h-48 md:h-full object-cover transform group-hover:scale-105 transition-transform duration-300"
/> />
</div> </div>
<div class="md:w-2/3 flex flex-col"> <div class="md:w-2/3 flex flex-col">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<span class="font-normal font-primary text-sm text-tertiary/70">{nicedate}</span> <span class="font-normal font-primary text-sm text-tertiary/70"
{location && ( >{nicedate}
</span>
{
location && (
<> <>
<span class="text-tertiary/40">|</span> <span class="text-tertiary/40">|</span>
<span class="font-normal font-primary text-sm text-tertiary/70">{location}</span> <span class="font-normal font-primary text-sm text-tertiary/70">
{location}
</span>
</> </>
)} )
}
</div> </div>
<h3 class="text-xl md:text-2xl font-bold font-secondary text-tertiary group-hover:text-tertiary/80 transition-colors mb-2"> <h3
class="text-xl md:text-2xl font-bold font-secondary text-tertiary group-hover:text-tertiary/80 transition-colors mb-2"
>
{data.data.title} {data.data.title}
</h3> </h3>
<p class="font-primary text-base text-tertiary/80 mb-4 line-clamp-3"> <p
class="font-primary text-base text-tertiary/80 mb-4 line-clamp-3"
>
{excerpt} {excerpt}
</p> </p>
{data.data.tags && data.data.tags.length > 0 && ( {
data.data.tags && data.data.tags.length > 0 && (
<div class="flex flex-nowrap md:flex-wrap gap-2 overflow-x-auto md:overflow-visible pb-2 md:pb-0 mb-4"> <div class="flex flex-nowrap md:flex-wrap gap-2 overflow-x-auto md:overflow-visible pb-2 md:pb-0 mb-4">
{data.data.tags.map((tag: string) => ( {data.data.tags.map((tag: string) => (
<span class="badge rounded-none bg-[#EBE6D2] border-none text-[#003421] whitespace-nowrap">{tag}</span> <span class="badge rounded-none bg-[#EBE6D2] border-none text-[#003421] whitespace-nowrap">
{tag}
</span>
))} ))}
</div> </div>
)} )
}
<div class="mt-auto"> <div class="mt-auto">
<span class="inline-flex items-center gap-1 text-sm font-primary text-tertiary font-semibold group-hover:underline"> <span
class="inline-flex items-center gap-1 text-sm font-primary text-tertiary font-semibold group-hover:underline"
>
{tl("news.seemore")} {tl("news.seemore")}
<Icon name="ph:arrow-right" class="transform group-hover:translate-x-1 transition-transform" /> <Icon
name="ph:arrow-right"
class="transform group-hover:translate-x-1 transition-transform"
/>
</span> </span>
</div> </div>
</div> </div>

View File

@ -15,6 +15,7 @@ const news = defineCollection({
state: z.string().optional(), state: z.string().optional(),
country: z.string().optional(), country: z.string().optional(),
thumbnail: image().optional().describe("Main news thumbnail image"), thumbnail: image().optional().describe("Main news thumbnail image"),
thumbnail_square: image().optional().describe("Main news thumbnail square image"),
youtube: z.string().optional(), youtube: z.string().optional(),
tags: z.array(z.string()).optional().describe("News tags"), tags: z.array(z.string()).optional().describe("News tags"),
gallery: z.array(z.object({ gallery: z.array(z.object({

View File

@ -5,7 +5,8 @@ date: 2025-01-04
slug: 2025-01-04-jerusalen-en-el-centro-del-2025-advertencias-sobre-un-ano-clave-para-el-cambio-global slug: 2025-01-04-jerusalen-en-el-centro-del-2025-advertencias-sobre-un-ano-clave-para-el-cambio-global
place: '' place: ''
country: 'PR' country: 'PR'
city: 'Puerto Rico' city: 'Cayey '
thumbnail_square: 'https://ik.imagekit.io/crpy/7K0B4402-1.webp?tr=w-1280,q-auto,f-auto'
thumbnail: 'https://ik.imagekit.io/crpy/7K0B4402.webp?tr=w-1280,q-auto,f-auto' thumbnail: 'https://ik.imagekit.io/crpy/7K0B4402.webp?tr=w-1280,q-auto,f-auto'
tags: [Israel, Puerto Rico] tags: [Israel, Puerto Rico]
gallery: [ gallery: [

View File

@ -5,7 +5,7 @@ date: 2025-02-16
slug: 2025-02-16-llamado-a-la-accion-internacional-el-dr-jose-benjamin-perez-matos-exige-respuestas-concretas-ante-las-crisis-en-israel-y-venezuela slug: 2025-02-16-llamado-a-la-accion-internacional-el-dr-jose-benjamin-perez-matos-exige-respuestas-concretas-ante-las-crisis-en-israel-y-venezuela
place: '' place: ''
country: 'PR' country: 'PR'
city: 'Puerto Rico' city: 'Cayey'
thumbnail: 'https://ik.imagekit.io/crpy/tr:w-1280/comunicado-1.webp' thumbnail: 'https://ik.imagekit.io/crpy/tr:w-1280/comunicado-1.webp'
tags: [Puerto Rico, Venezuela, Israel] tags: [Puerto Rico, Venezuela, Israel]
gallery: [ gallery: [

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":{"fullVersion":"2.2.5","major":"2","minor":"2","patch":"5"},"meta":{"flags":["experimentalData"]},"collections":[{"name":"news","label":"News","path":"src/content/news","format":"md","fields":[{"type":"string","name":"locale","label":"Language","options":[{"label":"Español","value":"es"},{"label":"English","value":"en"},{"label":"Português","value":"pt"},{"label":"Kinyarwanda","value":"rw"}],"namespace":["news","locale"],"searchable":true,"uid":false},{"type":"string","name":"title","label":"Title","isTitle":true,"required":true,"namespace":["news","title"],"searchable":true,"uid":false},{"type":"datetime","name":"date","label":"Date","required":true,"namespace":["news","date"],"searchable":true,"uid":false},{"type":"string","name":"slug","label":"Slug","namespace":["news","slug"],"searchable":true,"uid":false},{"type":"string","name":"place","label":"Place","namespace":["news","place"],"searchable":true,"uid":false},{"type":"string","name":"thumbnail","label":"Thumbnail URL","namespace":["news","thumbnail"],"searchable":true,"uid":false},{"type":"string","name":"youtube","label":"YouTube ID","namespace":["news","youtube"],"searchable":true,"uid":false},{"type":"string","name":"tags","label":"Tags (comma separated)","namespace":["news","tags"],"searchable":true,"uid":false},{"type":"boolean","name":"draft","label":"Draft","namespace":["news","draft"],"searchable":true,"uid":false},{"type":"rich-text","name":"body","label":"Content","isBody":true,"namespace":["news","body"],"searchable":true,"parser":{"type":"markdown"},"uid":false}],"namespace":["news"]},{"name":"documentaries","label":"Documentaries","path":"src/content/documentaries","format":"md","fields":[{"type":"string","name":"locale","label":"Language","options":[{"label":"Español","value":"es"},{"label":"English","value":"en"},{"label":"Português","value":"pt"},{"label":"Kinyarwanda","value":"rw"}],"namespace":["documentaries","locale"],"searchable":true,"uid":false},{"type":"string","name":"title","label":"Title","isTitle":true,"required":true,"namespace":["documentaries","title"],"searchable":true,"uid":false},{"type":"string","name":"video_yt","label":"YouTube ID","namespace":["documentaries","video_yt"],"searchable":true,"uid":false},{"type":"datetime","name":"date","label":"Date","required":true,"namespace":["documentaries","date"],"searchable":true,"uid":false}],"namespace":["documentaries"]}],"config":{"media":{"tina":{"publicFolder":"public","mediaRoot":"public/images"}}}} {"version":{"fullVersion":"2.2.5","major":"2","minor":"2","patch":"5"},"meta":{"flags":["experimentalData"]},"collections":[{"name":"news","label":"News","path":"src/content/news","format":"md","fields":[{"type":"string","name":"locale","label":"Language","options":[{"label":"Español","value":"es"},{"label":"English","value":"en"},{"label":"Português","value":"pt"},{"label":"Kinyarwanda","value":"rw"}],"namespace":["news","locale"],"searchable":true,"uid":false},{"type":"string","name":"title","label":"Title","isTitle":true,"required":true,"namespace":["news","title"],"searchable":true,"uid":false},{"type":"datetime","name":"date","label":"Date","required":true,"namespace":["news","date"],"searchable":true,"uid":false},{"type":"string","name":"slug","label":"Slug","namespace":["news","slug"],"searchable":true,"uid":false},{"type":"string","name":"place","label":"Place","namespace":["news","place"],"searchable":true,"uid":false},{"type":"string","name":"thumbnail","label":"Thumbnail URL","namespace":["news","thumbnail"],"searchable":true,"uid":false},{"type":"string","name":"thumbnail_square","label":"Thumbnail Square URL","namespace":["news","thumbnail_square"],"searchable":true,"uid":false},{"type":"string","name":"youtube","label":"YouTube ID","namespace":["news","youtube"],"searchable":true,"uid":false},{"type":"string","name":"tags","label":"Tags (comma separated)","namespace":["news","tags"],"searchable":true,"uid":false},{"type":"boolean","name":"draft","label":"Draft","namespace":["news","draft"],"searchable":true,"uid":false},{"type":"rich-text","name":"body","label":"Content","isBody":true,"namespace":["news","body"],"searchable":true,"parser":{"type":"markdown"},"uid":false}],"namespace":["news"]},{"name":"documentaries","label":"Documentaries","path":"src/content/documentaries","format":"md","fields":[{"type":"string","name":"locale","label":"Language","options":[{"label":"Español","value":"es"},{"label":"English","value":"en"},{"label":"Português","value":"pt"},{"label":"Kinyarwanda","value":"rw"}],"namespace":["documentaries","locale"],"searchable":true,"uid":false},{"type":"string","name":"title","label":"Title","isTitle":true,"required":true,"namespace":["documentaries","title"],"searchable":true,"uid":false},{"type":"string","name":"video_yt","label":"YouTube ID","namespace":["documentaries","video_yt"],"searchable":true,"uid":false},{"type":"datetime","name":"date","label":"Date","required":true,"namespace":["documentaries","date"],"searchable":true,"uid":false}],"namespace":["documentaries"]}],"config":{"media":{"tina":{"publicFolder":"public","mediaRoot":"public/images"}}}}

View File

@ -62,6 +62,11 @@ var config_default = defineConfig({
name: "thumbnail", name: "thumbnail",
label: "Thumbnail URL" label: "Thumbnail URL"
}, },
{
type: "string",
name: "thumbnail_square",
label: "Thumbnail Square URL"
},
{ {
type: "string", type: "string",
name: "youtube", name: "youtube",

View File

@ -6,6 +6,7 @@ fragment NewsParts on News {
slug slug
place place
thumbnail thumbnail
thumbnail_square
youtube youtube
tags tags
draft draft

View File

@ -97,6 +97,7 @@ type News implements Node & Document {
slug: String slug: String
place: String place: String
thumbnail: String thumbnail: String
thumbnail_square: String
youtube: String youtube: String
tags: String tags: String
draft: Boolean draft: Boolean
@ -139,6 +140,7 @@ input NewsFilter {
slug: StringFilter slug: StringFilter
place: StringFilter place: StringFilter
thumbnail: StringFilter thumbnail: StringFilter
thumbnail_square: StringFilter
youtube: StringFilter youtube: StringFilter
tags: StringFilter tags: StringFilter
draft: BooleanFilter draft: BooleanFilter
@ -214,6 +216,7 @@ input NewsMutation {
slug: String slug: String
place: String place: String
thumbnail: String thumbnail: String
thumbnail_square: String
youtube: String youtube: String
tags: String tags: String
draft: Boolean draft: Boolean

View File

@ -191,6 +191,7 @@ export type News = Node & Document & {
slug?: Maybe<Scalars['String']['output']>; slug?: Maybe<Scalars['String']['output']>;
place?: Maybe<Scalars['String']['output']>; place?: Maybe<Scalars['String']['output']>;
thumbnail?: Maybe<Scalars['String']['output']>; thumbnail?: Maybe<Scalars['String']['output']>;
thumbnail_square?: Maybe<Scalars['String']['output']>;
youtube?: Maybe<Scalars['String']['output']>; youtube?: Maybe<Scalars['String']['output']>;
tags?: Maybe<Scalars['String']['output']>; tags?: Maybe<Scalars['String']['output']>;
draft?: Maybe<Scalars['Boolean']['output']>; draft?: Maybe<Scalars['Boolean']['output']>;
@ -233,6 +234,7 @@ export type NewsFilter = {
slug?: InputMaybe<StringFilter>; slug?: InputMaybe<StringFilter>;
place?: InputMaybe<StringFilter>; place?: InputMaybe<StringFilter>;
thumbnail?: InputMaybe<StringFilter>; thumbnail?: InputMaybe<StringFilter>;
thumbnail_square?: InputMaybe<StringFilter>;
youtube?: InputMaybe<StringFilter>; youtube?: InputMaybe<StringFilter>;
tags?: InputMaybe<StringFilter>; tags?: InputMaybe<StringFilter>;
draft?: InputMaybe<BooleanFilter>; draft?: InputMaybe<BooleanFilter>;
@ -371,6 +373,7 @@ export type NewsMutation = {
slug?: InputMaybe<Scalars['String']['input']>; slug?: InputMaybe<Scalars['String']['input']>;
place?: InputMaybe<Scalars['String']['input']>; place?: InputMaybe<Scalars['String']['input']>;
thumbnail?: InputMaybe<Scalars['String']['input']>; thumbnail?: InputMaybe<Scalars['String']['input']>;
thumbnail_square?: InputMaybe<Scalars['String']['input']>;
youtube?: InputMaybe<Scalars['String']['input']>; youtube?: InputMaybe<Scalars['String']['input']>;
tags?: InputMaybe<Scalars['String']['input']>; tags?: InputMaybe<Scalars['String']['input']>;
draft?: InputMaybe<Scalars['Boolean']['input']>; draft?: InputMaybe<Scalars['Boolean']['input']>;
@ -384,7 +387,7 @@ export type DocumentariesMutation = {
date?: InputMaybe<Scalars['String']['input']>; date?: InputMaybe<Scalars['String']['input']>;
}; };
export type NewsPartsFragment = { __typename: 'News', locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null }; export type NewsPartsFragment = { __typename: 'News', locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, thumbnail_square?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null };
export type DocumentariesPartsFragment = { __typename: 'Documentaries', locale?: string | null, title: string, video_yt?: string | null, date: string }; export type DocumentariesPartsFragment = { __typename: 'Documentaries', locale?: string | null, title: string, video_yt?: string | null, date: string };
@ -393,7 +396,7 @@ export type NewsQueryVariables = Exact<{
}>; }>;
export type NewsQuery = { __typename?: 'Query', news: { __typename: 'News', id: string, locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null, _sys: { __typename?: 'SystemInfo', filename: string, basename: string, hasReferences?: boolean | null, breadcrumbs: Array<string>, path: string, relativePath: string, extension: string } } }; export type NewsQuery = { __typename?: 'Query', news: { __typename: 'News', id: string, locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, thumbnail_square?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null, _sys: { __typename?: 'SystemInfo', filename: string, basename: string, hasReferences?: boolean | null, breadcrumbs: Array<string>, path: string, relativePath: string, extension: string } } };
export type NewsConnectionQueryVariables = Exact<{ export type NewsConnectionQueryVariables = Exact<{
before?: InputMaybe<Scalars['String']['input']>; before?: InputMaybe<Scalars['String']['input']>;
@ -405,7 +408,7 @@ export type NewsConnectionQueryVariables = Exact<{
}>; }>;
export type NewsConnectionQuery = { __typename?: 'Query', newsConnection: { __typename?: 'NewsConnection', totalCount: number, pageInfo: { __typename?: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string, endCursor: string }, edges?: Array<{ __typename?: 'NewsConnectionEdges', cursor: string, node?: { __typename: 'News', id: string, locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null, _sys: { __typename?: 'SystemInfo', filename: string, basename: string, hasReferences?: boolean | null, breadcrumbs: Array<string>, path: string, relativePath: string, extension: string } } | null } | null> | null } }; export type NewsConnectionQuery = { __typename?: 'Query', newsConnection: { __typename?: 'NewsConnection', totalCount: number, pageInfo: { __typename?: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string, endCursor: string }, edges?: Array<{ __typename?: 'NewsConnectionEdges', cursor: string, node?: { __typename: 'News', id: string, locale?: string | null, title: string, date: string, slug?: string | null, place?: string | null, thumbnail?: string | null, thumbnail_square?: string | null, youtube?: string | null, tags?: string | null, draft?: boolean | null, body?: any | null, _sys: { __typename?: 'SystemInfo', filename: string, basename: string, hasReferences?: boolean | null, breadcrumbs: Array<string>, path: string, relativePath: string, extension: string } } | null } | null> | null } };
export type DocumentariesQueryVariables = Exact<{ export type DocumentariesQueryVariables = Exact<{
relativePath: Scalars['String']['input']; relativePath: Scalars['String']['input'];
@ -435,6 +438,7 @@ export const NewsPartsFragmentDoc = gql`
slug slug
place place
thumbnail thumbnail
thumbnail_square
youtube youtube
tags tags
draft draft

View File

@ -63,6 +63,11 @@ export default defineConfig({
name: "thumbnail", name: "thumbnail",
label: "Thumbnail URL", label: "Thumbnail URL",
}, },
{
type: "string",
name: "thumbnail_square",
label: "Thumbnail Square URL",
},
{ {
type: "string", type: "string",
name: "youtube", name: "youtube",

File diff suppressed because one or more lines are too long