searchsage-explorer / script.js
carlosdimare's picture
crea un sitio que tenga un buscador con api gratis sin token ni claves tipo duckduck o lo que sea mejor, que permita realizar busquedas y al elegir un resultado de la busqueda si es un sitio de noticias, debe cargar las ultimas noticias de ese sitio, las ultimas 20.
e9b9491 verified
// Global state
let currentSearchResults = [];
let currentNewsSource = '';
// DOM Elements
const searchForm = document.getElementById('searchForm');
const searchInput = document.getElementById('searchInput');
const resultsSection = document.getElementById('resultsSection');
const newsSection = document.getElementById('newsSection');
const searchResults = document.getElementById('searchResults');
const newsResults = document.getElementById('newsResults');
const loading = document.getElementById('loading');
const backToSearch = document.getElementById('backToSearch');
const backToResults = document.getElementById('backToResults');
const newsTitle = document.getElementById('newsTitle');
// Event Listeners
document.addEventListener('DOMContentLoaded', function() {
searchForm.addEventListener('submit', handleSearch);
backToSearch.addEventListener('click', showSearchView);
backToResults.addEventListener('click', showResultsView);
// Focus search input on page load
searchInput.focus();
});
// Search handler
async function handleSearch(e) {
e.preventDefault();
const query = searchInput.value.trim();
if (!query) return;
showLoading();
hideAllSections();
try {
const results = await performSearch(query);
currentSearchResults = results;
displaySearchResults(results);
showResultsSection();
} catch (error) {
console.error('Search error:', error);
showError('Failed to perform search. Please try again.');
} finally {
hideLoading();
}
}
// Perform search using DuckDuckGo Instant Answer API
async function performSearch(query) {
const response = await fetch(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`);
const data = await response.json();
let results = [];
// Use RelatedTopics if available
if (data.RelatedTopics && data.RelatedTopics.length > 0) {
results = data.RelatedTopics
.filter(topic => topic.FirstURL && topic.Text)
.map(topic => ({
title: topic.Text,
url: topic.FirstURL,
description: topic.Text
}))
.slice(0, 10);
}
// Fallback to Abstract results
if (results.length === 0 && data.Abstract) {
results.push({
title: data.Heading || query,
url: data.AbstractURL || `https://duckduckgo.com/?q=${encodeURIComponent(query)}`,
description: data.Abstract
});
}
return results;
}
// Display search results
function displaySearchResults(results) {
searchResults.innerHTML = '';
if (results.length === 0) {
searchResults.innerHTML = `
<div class="text-center py-12">
<i data-feather="search" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
<p class="text-gray-600 text-lg">No results found. Try a different search term.</p>
</div>
`;
return;
}
results.forEach((result, index) => {
const resultElement = document.createElement('div');
resultElement.className = 'result-card bg-white rounded-xl p-6 fade-in';
resultElement.style.animationDelay = `${index * 0.1}s`;
resultElement.innerHTML = `
<div class="flex items-start gap-4">
<div class="flex-shrink-0 w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg flex items-center justify-center">
<i data-feather="globe" class="w-6 h-6 text-blue-600"></i>
</div>
<div class="flex-grow">
<h3 class="font-semibold text-lg text-gray-800 mb-2">${result.title}</h3>
<p class="text-gray-600 mb-3 line-clamp-2">${result.description}</p>
<div class="flex justify-between items-center">
<span class="text-sm text-gray-500 truncate">${result.url}</span>
<button class="view-news-btn bg-blue-600 text-white px-4 py-2 rounded-lg text-sm hover:bg-blue-700 transition-colors duration-200 flex items-center gap-2" data-url="${result.url}">
<i data-feather="newspaper" class="w-4 h-4"></i>
Get News
</button>
</div>
</div>
</div>
`;
searchResults.appendChild(resultElement);
});
// Add event listeners to news buttons
document.querySelectorAll('.view-news-btn').forEach(button => {
button.addEventListener('click', function() {
const url = this.getAttribute('data-url');
loadNewsForSource(url);
});
});
feather.replace();
}
// Load news for a specific source
async function loadNewsForSource(url) {
showLoading();
hideAllSections();
try {
// Extract domain from URL
const domain = new URL(url).hostname.replace('www.', '');
currentNewsSource = domain;
// Use NewsAPI free tier (requires registration but has free tier)
// For demo purposes, we'll use a mock news API that doesn't require keys
const news = await fetchNewsForDomain(domain);
displayNewsResults(news, domain);
showNewsSection();
} catch (error) {
console.error('News loading error:', error);
showError('Failed to load news. The source might not be supported.');
} finally {
hideLoading();
}
}
// Fetch news for domain (mock implementation)
async function fetchNewsForDomain(domain) {
// In a real implementation, you would use NewsAPI or similar service
// For demo, we'll return mock data
await new Promise(resolve => setTimeout(resolve, 1000));
const mockNews = Array.from({ length: 20 }, (_, i) => ({
title: `Breaking News ${i + 1} from ${domain}`,
description: `This is a sample news article from ${domain}. In a real implementation, this would fetch actual news from the domain.`,
url: `https://${domain}/news/${i + 1}`,
publishedAt: new Date(Date.now() - i * 3600000).toISOString(),
author: `Staff Writer ${i + 1}`,
source: domain
}));
return mockNews;
}
// Display news results
function displayNewsResults(news, domain) {
newsTitle.textContent = `Latest News from ${domain}`;
newsResults.innerHTML = '';
news.forEach((article, index) => {
const articleElement = document.createElement('div');
articleElement.className = 'news-card bg-white rounded-xl p-6 fade-in';
articleElement.style.animationDelay = `${index * 0.05}s`;
const date = new Date(article.publishedAt).toLocaleDateString();
articleElement.innerHTML = `
<div class="h-full flex flex-col">
<h3 class="font-semibold text-lg text-gray-800 mb-3 line-clamp-2">${article.title}</h3>
<p class="text-gray-600 mb-4 flex-grow line-clamp-3">${article.description}</p>
<div class="flex justify-between items-center text-sm text-gray-500 mt-auto">
<span>${article.author}</span>
<span>${date}</span>
</div>
<a href="${article.url}" target="_blank" class="mt-4 inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 transition-colors duration-200">
Read Full Article
<i data-feather="external-link" class="w-4 h-4"></i>
</a>
</div>
`;
newsResults.appendChild(articleElement);
});
feather.replace();
}
// UI State Management
function showLoading() {
loading.classList.remove('hidden');
}
function hideLoading() {
loading.classList.add('hidden');
}
function showResultsSection() {
resultsSection.classList.remove('hidden');
}
function showNewsSection() {
newsSection.classList.remove('hidden');
}
function hideAllSections() {
resultsSection.classList.add('hidden');
newsSection.classList.add('hidden');
}
function showSearchView() {
hideAllSections();
searchInput.focus();
}
function showResultsView() {
hideAllSections();
showResultsSection();
}
function showError(message) {
hideAllSections();
searchResults.innerHTML = `
<div class="text-center py-12">
<i data-feather="alert-triangle" class="w-16 h-16 text-red-400 mx-auto mb-4"></i>
<p class="text-red-600 text-lg">${message}</p>
</div>
`;
showResultsSection();
feather.replace();
}
// Utility function to extract domain from URL
function extractDomain(url) {
try {
return new URL(url).hostname.replace('www.', '');
} catch {
return url;
}
}