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; | |
| } | |
| } |