Russian drones and missiles target Ukraine's eastern city of Kharkiv, killing 3, officials say
3e713p
The barrage — the latest in near daily widescale attacks — included aerial glide bombs that have become part of a fierce Russian onslaught on Ukraine…
More →
Colombian authorities carry out rescues after rafts and kayak capsize in deadly flash flooding
z2u1x
Several rafts and a kayak capsized Friday on the Güejar River in central Colombia due to a sudden flood that left at least four people dead, the Nati…
More →
Riot police and anti-ICE protesters square off in LA
6v2j4g
Protests erupted in Los Angeles after ICE agents targeted several locations as a part of President Trump's crackdown on illegal immigration.
More →
Google's DolphinGemma: AI Unlocks the Secrets of Dolphin Communication
f5z5b
In a groundbreaking intersection of artificial intelligence and marine biology, Google has unveiled DolphinGemma, an advanced AI model designed to an…
More →
CCTV video shows moment of Russian drone strike in Kharkiv, Ukraine
2w404s
Seventeen people were wounded in a Russian drone strike on the eastern Ukrainian city of Kharkiv early Thursday, including children, a pregnant woman…
More →
Reaction to recovery of bodies of two Israeli-American hostages from Gaza
2x185m
Israel has recovered the bodies of two Israeli-American hostages taken in Hamas’ October 7, 2023, attack that ignited the war in the Gaza Strip.
More →
Trump announces travel ban and restrictions on 19 countries
4hk3e
President Donald Trump has signed a travel ban on 12 countries, primarily in Africa and the Middle East, from coming to America.
More →
Trump suspends entry of international students studying at Harvard
5b6qq
US President Donald Trump suspended for an initial six months the entry into the United States of foreign nationals seeking to study or participate i…
More →
The BTS effect: K-beauty brands target US despite tariffs
6u3o5n
On the back of the popularity for cultural exports such as K-Pop and their roaring online sales through Amazon, South Korea's cosmetic startups a…
More →
`;
container.appendChild(feedSection);
const targetElement = document.getElementById(feedConfig.targetElementId);
if (!targetElement) {
console.error(`Elemento target no encontrado: ${feedConfig.targetElementId}`);
const errorDiv = document.createElement('div');
errorDiv.className = 'error-rss';
errorDiv.innerHTML = `Error de configuración: Contenedor para "${sanitizeHtml(feedConfig.title)}" no encontrado.`;
// Adjuntar al feedSection en lugar de al container principal para errores por feed
feedSection.querySelector('.rss-items-wrapper').innerHTML = errorDiv.outerHTML;
return;
}
try {
const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedConfig.url)}`;
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`Error de red/servidor (${response.status}) al ar rss2json para ${sanitizeHtml(feedConfig.title)}.`);
}
const data = await response.json();
if (data.status !== 'ok') {
// Intenta mostrar el mensaje de error de rss2json si está disponible
const apiErrorMessage = data.message ? `API Error: ${data.message}` : `Error desconocido de rss2json`;
throw new Error(`${apiErrorMessage} para ${sanitizeHtml(feedConfig.title)}.`);
}
if (!data.items || data.items.length === 0) {
targetElement.innerHTML = '';
return;
}
targetElement.innerHTML = ''; // Limpiar "Cargando"
const items = data.items.slice(0, maxItemsPerFeed);
items.forEach((item, index) => {
const title = sanitizeHtml(item.title || 'Sin título');
const link = item.link || '#';
const rawSnippet = item.description || item.content || ''; // rss2json pone el contenido en description
const cleanSnippet = sanitizeHtml(rawSnippet);
const truncatedSnippet = truncateText(cleanSnippet, snippetMaxLength);
const imageUrl = extractImageUrl(item); // Usa la función modificada
const itemDiv = document.createElement('div');
itemDiv.className = 'rss-item';
// onerror para imagen: intenta cargar el placeholder si la imagen principal falla
const imageOnErrorScript = `this.onerror=null; this.src='${placeholderImageUrl}'; this.classList.add('image-load-error');`;
// Si quieres el layout de "1 grande, 2 pequeños" DENTRO de CADA feed
// necesitarías modificar la lógica de cómo se añaden los items aquí.
// Por ahora, mantendré la lógica original de este script que parece ser:
// - El primer item (.featured-item) se añade directamente.
// - Los siguientes items se agrupan en un .small-item-container con .small-item.
// Esta lógica ya estaba en tu script, la he mantenido.
let itemHTML = `
`;
// La lógica de featured-item y small-item-container ya estaba en tu script.
// Si quieres el layout 1 grande, 2 pequeños, 1 grande, 2 pequeños etc.
// esta lógica debería funcionar para el primer grupo (1 grande, N-1 pequeños).
// Si maxItemsPerFeed es 3, tendrás 1 grande y 2 pequeños.
if (index === 0 && maxItemsPerFeed > 1) { // Solo si hay más de 1 item, el primero es "featured"
itemDiv.classList.add('featured-rss-item'); // Renombrada para evitar colisión con AFCM
itemDiv.innerHTML = itemHTML;
targetElement.appendChild(itemDiv);
} else {
// Agrupar los siguientes en un contenedor si no existe ya para este feed
let smallItemsPairContainer = targetElement.querySelector('.secondary-rss-items-pair');
if (!smallItemsPairContainer) {
smallItemsPairContainer = document.createElement('div');
smallItemsPairContainer.className = 'secondary-rss-items-pair'; // Contenedor para pares
targetElement.appendChild(smallItemsPairContainer);
}
const secondaryItemDiv = document.createElement('div');
secondaryItemDiv.className = 'rss-item secondary-rss-item'; // Ítem individual del par
secondaryItemDiv.innerHTML = itemHTML;
smallItemsPairContainer.appendChild(secondaryItemDiv);
}
});
} catch (error) {
console.error(`Error al obtener o procesar el feed "${sanitizeHtml(feedConfig.title)}":`, error.message, error);
targetElement.innerHTML = ``;
}
}
if (mainWidgetContainer) {
if (feedsConfig && feedsConfig.length > 0) {
feedsConfig.forEach((config) => { // No es necesario `async` aquí en el forEach si `fetchAndDisplayFeed` es `async`
fetchAndDisplayFeed(config, mainWidgetContainer); // No esperamos aquí, dejamos que se ejecuten en paralelo
});
// El separador
se añade después de que todos los fetchAndDisplayFeed se hayan INICIADO,
// no necesariamente completado. Si el orden es crítico, se necesitaría un enfoque diferente.
// Para simplificar y asegurar que el
se añade después de cada sección de feed:
// (Esta parte del separador es un poco compleja de hacer bien con async/await en un forEach,
// la forma original de añadirlo podría ser más robusta si el orden importa visualmente)
// Por ahora, lo eliminamos de aquí para no complicar y lo gestionamos con CSS (gap en #dual-rss)
} else {
mainWidgetContainer.innerHTML = "";
}
} else {
console.error("Contenedor principal del widget 'dual-rss' no encontrado.");
}
});
//]]>
Follow us on social networks! 2e3f1k