409 lines
No EOL
20 KiB
HTML
409 lines
No EOL
20 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Descubrir Feeds RSS{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="card feed-detail-card"
|
|
style="padding: 40px; border-radius: 15px; background-color: #fdfdfd; box-shadow: 0 10px 30px rgba(0,0,0,0.05);">
|
|
<h1
|
|
style="font-family: var(--primary-font); font-weight: 700; margin-bottom: 30px; border-bottom: 2px solid var(--accent-color); display: inline-block; padding-bottom: 10px;">
|
|
<i class="fas fa-search"></i> Descubrir Feeds RSS
|
|
</h1>
|
|
|
|
<p style="margin-bottom: 30px; color: #666;">
|
|
Ingresa la URL de un sitio web y automáticamente descubriremos todos los feeds RSS disponibles.
|
|
</p>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="searching-overlay"
|
|
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.9); z-index: 1000; flex-direction: column; align-items: center; justify-content: center;">
|
|
<div class="spinner"
|
|
style="border: 4px solid #f3f3f3; border-top: 4px solid var(--accent-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite;">
|
|
</div>
|
|
<h3 style="margin-top: 20px; color: #333;">Analizando sitio web...</h3>
|
|
<p style="color: #666;">Buscando feeds RSS, esto puede tardar unos segundos.</p>
|
|
</div>
|
|
|
|
<!-- Discovery Form -->
|
|
<form action="{{ url_for('feeds.discover_feed') }}" method="post" class="form-grid" style="margin-bottom: 40px;"
|
|
onsubmit="document.getElementById('searching-overlay').style.display = 'flex';">
|
|
<div class="form-row">
|
|
<label for="source_url">URL del sitio web</label>
|
|
<input type="url" id="source_url" name="source_url" placeholder="https://ejemplo.com"
|
|
value="{{ source_url }}" required style="font-size: 16px;">
|
|
</div>
|
|
|
|
<div class="form-row" style="border: none; padding-top: 20px;">
|
|
<div></div>
|
|
<div class="form-actions">
|
|
<button class="btn btn-primary" type="submit">
|
|
<i class="fas fa-search"></i> Buscar Feeds
|
|
</button>
|
|
<a href="{{ url_for('feeds.list_feeds') }}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Volver
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Results -->
|
|
{% if discovered_feeds %}
|
|
<hr style="margin: 40px 0; border: none; border-top: 1px solid #e0e0e0;">
|
|
|
|
{% set new_feeds_count = discovered_feeds | rejectattr('exists') | list | length %}
|
|
<h2 style="font-size: 24px; margin-bottom: 20px; color: #333;">
|
|
<i class="fas fa-rss"></i> Feeds Disponibles: <strong>{{ new_feeds_count }}</strong> <span
|
|
style="font-size: 16px; color: #777; font-weight: normal;">(de {{ discovered_feeds|length }} encontrados en
|
|
total)</span>
|
|
</h2>
|
|
|
|
<form action="{{ url_for('feeds.discover_and_add') }}" method="post">
|
|
<!-- Global Settings -->
|
|
<div class="form-grid"
|
|
style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 30px; border: 1px solid #e0e0e0;">
|
|
<h3
|
|
style="grid-column: 1 / -1; font-size: 16px; margin-bottom: 15px; color: #555; text-transform: uppercase; font-weight: 700; letter-spacing: 0.5px;">
|
|
<i class="fas fa-sliders-h"></i> Configuración Masiva
|
|
</h3>
|
|
|
|
<div class="form-row">
|
|
<label for="global_categoria_id">Aplicar Categoría a todos:</label>
|
|
<div style="display: flex; gap: 10px;">
|
|
<select id="global_categoria_id" class="form-control">
|
|
<option value="">— Seleccionar —</option>
|
|
{% for c in categorias %}
|
|
<option value="{{ c.id }}">{{ c.nombre }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="applyGlobalCategory()"
|
|
title="Aplicar a todos">
|
|
<i class="fas fa-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="global_pais_id">Aplicar País a todos:</label>
|
|
<div style="display: flex; gap: 10px;">
|
|
<select id="global_pais_id" class="form-control">
|
|
<option value="">— Seleccionar —</option>
|
|
{% for p in paises %}
|
|
<option value="{{ p.id }}">{{ p.nombre }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="applyGlobalCountry()"
|
|
title="Aplicar a todos">
|
|
<i class="fas fa-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<label for="global_idioma">Aplicar Idioma a todos:</label>
|
|
<div style="display: flex; gap: 10px;">
|
|
<input type="text" id="global_idioma" class="form-control" maxlength="5" placeholder="es" value="es"
|
|
style="width: 80px;">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="applyGlobalLanguage()"
|
|
title="Aplicar a todos">
|
|
<i class="fas fa-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Feed List -->
|
|
<div style="margin-bottom: 30px;">
|
|
{% for feed in discovered_feeds %}
|
|
<div class="feed-discovery-item" style="
|
|
background: {{ 'white' if feed.valid and not feed.exists else ('#f0f0f0' if feed.exists else '#fff5f5') }};
|
|
border: 1px solid {{ '#e0e0e0' if feed.valid else '#ffcdd2' }};
|
|
border-left: 5px solid {{ '#4CAF50' if feed.valid and not feed.exists else ('#9e9e9e' if feed.exists else '#ff5252') }};
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
display: grid;
|
|
grid-template-columns: 40px 1fr 280px auto;
|
|
gap: 20px;
|
|
align-items: start;
|
|
transition: all 0.2s ease;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
|
opacity: {{ '0.8' if feed.exists else '1' }};
|
|
">
|
|
<!-- Checkbox -->
|
|
<div style="padding-top: 5px; display: flex; justify-content: center;">
|
|
{% if feed.exists %}
|
|
<i class="fas fa-check-circle" style="color: #4CAF50; font-size: 22px;"
|
|
title="Ya existe en la base de datos"></i>
|
|
{% elif feed.valid %}
|
|
<input type="checkbox" name="selected_feeds" value="{{ feed.url }}" id="feed_{{ loop.index }}"
|
|
checked style="width: 22px; height: 22px; cursor: pointer; border-radius: 4px;">
|
|
<input type="hidden" name="context_{{ feed.url }}" value="{{ feed.context_label }}">
|
|
{% else %}
|
|
<i class="fas fa-exclamation-triangle" style="color: #ff5252; font-size: 20px;"
|
|
title="{{ feed.error }}"></i>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Feed Info -->
|
|
<div>
|
|
<label for="feed_{{ loop.index }}" style="cursor: pointer; display: block; margin-bottom: 8px;">
|
|
<strong
|
|
style="font-size: 18px; color: #{{ '555' if feed.exists else '333' }}; line-height: 1.3;">
|
|
{{ feed.title }}
|
|
</strong>
|
|
{% if feed.exists %}
|
|
<span class="badge"
|
|
style="background: #e0e0e0; color: #555; font-size: 11px; vertical-align: middle; margin-left: 8px;">YA
|
|
INSTALADO</span>
|
|
{% endif %}
|
|
</label>
|
|
|
|
{% if feed.description %}
|
|
<p style="color: #666; margin-bottom: 12px; font-size: 14px; line-height: 1.5;">
|
|
{{ feed.description[:250] }}{% if feed.description|length > 250 %}...{% endif %}
|
|
</p>
|
|
{% endif %}
|
|
|
|
<div style="font-size: 13px; color: #888; display: flex; flex-direction: column; gap: 6px;">
|
|
<div>
|
|
<i class="fas fa-link" style="width: 16px; text-align: center;"></i>
|
|
<a href="{{ feed.url }}" target="_blank"
|
|
style="color: #888; text-decoration: none; border-bottom: 1px dotted #ccc;">
|
|
{{ feed.url[:60] }}{% if feed.url|length > 60 %}...{% endif %}
|
|
</a>
|
|
</div>
|
|
|
|
{% if feed.context_label %}
|
|
<div style="color: #1976D2; font-weight: 500;">
|
|
<i class="fas fa-tag" style="width: 16px; text-align: center;"></i> Encontrado en: "{{
|
|
feed.context_label }}"
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if feed.valid %}
|
|
<div style="display: flex; gap: 15px; margin-top: 5px;">
|
|
{% if feed.type %}
|
|
<span class="badge"
|
|
style="background: #e3f2fd; color: #1565c0; padding: 2px 8px; border-radius: 4px;">
|
|
{{ feed.type|upper }}
|
|
</span>
|
|
{% endif %}
|
|
{% if feed.entry_count is defined %}
|
|
<span class="badge"
|
|
style="background: #f3e5f5; color: #7b1fa2; padding: 2px 8px; border-radius: 4px;">
|
|
{{ feed.entry_count }} items
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div style="color: #d32f2f; margin-top: 5px;">
|
|
<i class="fas fa-info-circle"></i> Error: {{ feed.error }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Individual Configurations -->
|
|
{% if feed.valid %}
|
|
<div style="background: #fdfdfd; padding: 15px; border-radius: 8px; border: 1px solid #eee;">
|
|
<div style="margin-bottom: 10px;">
|
|
<label
|
|
style="font-size: 12px; font-weight: 600; color: #555; display: block; margin-bottom: 4px;">Categoría</label>
|
|
<select name="cat_{{ feed.url }}" class="item-category-select"
|
|
style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;">
|
|
<option value="">— Seleccionar —</option>
|
|
{% for c in categorias %}
|
|
<option value="{{ c.id }}">{{ c.nombre }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div style="margin-bottom: 10px;">
|
|
<label
|
|
style="font-size: 12px; font-weight: 600; color: #555; display: block; margin-bottom: 4px;">País</label>
|
|
<select name="country_{{ feed.url }}" class="item-country-select"
|
|
style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;">
|
|
<option value="">— Seleccionar —</option>
|
|
{% for p in paises %}
|
|
<option value="{{ p.id }}">{{ p.nombre }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label
|
|
style="font-size: 12px; font-weight: 600; color: #555; display: block; margin-bottom: 4px;">Idioma</label>
|
|
<input type="text" name="lang_{{ feed.url }}" class="item-language-input" value="es"
|
|
maxlength="5"
|
|
style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;">
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div></div>
|
|
{% endif %}
|
|
|
|
<!-- Actions -->
|
|
<div style="display: flex; flex-direction: column; gap: 10px; justify-content: flex-start;">
|
|
{% if feed.valid %}
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="addSingleFeed('{{ feed.url }}')"
|
|
style="white-space: nowrap; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
|
|
<i class="fas fa-plus"></i> Añadir
|
|
</button>
|
|
<a href="{{ feed.url }}" target="_blank" class="btn btn-outline-secondary btn-sm"
|
|
style="white-space: nowrap;">
|
|
<i class="fas fa-external-link-alt"></i> Ver XML
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="form-actions"
|
|
style="display: flex; gap: 15px; justify-content: flex-end; padding-top: 20px; border-top: 1px solid #e0e0e0; position: sticky; bottom: 0; background: white; z-index: 10; padding-bottom: 20px;">
|
|
<div style="margin-right: auto; align-self: center; color: #666; font-size: 14px;">
|
|
<span id="selected_count">{{ discovered_feeds|selectattr('valid')|list|length }}</span> feeds
|
|
seleccionados
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" onclick="toggleAllFeeds(true)">
|
|
<i class="fas fa-check-square"></i> Todos
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" onclick="toggleAllFeeds(false)">
|
|
<i class="fas fa-square"></i> Ninguno
|
|
</button>
|
|
<button class="btn btn-primary" type="submit"
|
|
style="padding: 10px 25px; font-weight: 600; box-shadow: 0 4px 10px rgba(0,0,0,0.1);">
|
|
<i class="fas fa-plus-circle"></i> AÑADIR SELECCIONADOS
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
function toggleAllFeeds(select) {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_feeds"]');
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = select;
|
|
});
|
|
updateCount();
|
|
}
|
|
|
|
function addSingleFeed(url) {
|
|
// Collect specific values
|
|
const cat = document.querySelector(`select[name="cat_${url}"]`).value;
|
|
const country = document.querySelector(`select[name="country_${url}"]`).value;
|
|
const lang = document.querySelector(`input[name="lang_${url}"]`).value;
|
|
const context = document.querySelector(`input[name="context_${url}"]`) ? document.querySelector(`input[name="context_${url}"]`).value : '';
|
|
|
|
const formData = new FormData();
|
|
formData.append('selected_feeds', url);
|
|
formData.append(`cat_${url}`, cat);
|
|
formData.append(`country_${url}`, country);
|
|
formData.append(`lang_${url}`, lang);
|
|
if (context) formData.append(`context_${url}`, context);
|
|
|
|
const btn = event.target.closest('button');
|
|
const originalContent = btn.innerHTML;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ...';
|
|
btn.disabled = true;
|
|
|
|
fetch('{{ url_for("feeds.discover_and_add") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
btn.innerHTML = '<i class="fas fa-check"></i> Añadido';
|
|
btn.classList.remove('btn-primary');
|
|
btn.classList.add('btn-success');
|
|
// Disable inputs for this row
|
|
document.querySelector(`select[name="cat_${url}"]`).disabled = true;
|
|
document.querySelector(`select[name="country_${url}"]`).disabled = true;
|
|
document.querySelector(`input[name="lang_${url}"]`).disabled = true;
|
|
} else {
|
|
btn.innerHTML = '<i class="fas fa-times"></i> Error';
|
|
btn.classList.remove('btn-primary');
|
|
btn.classList.add('btn-danger');
|
|
alert('No se pudo añadir: ' + (data.errors ? data.errors.join(', ') : 'Error desconocido'));
|
|
setTimeout(() => {
|
|
btn.innerHTML = originalContent;
|
|
btn.disabled = false;
|
|
btn.classList.remove('btn-danger');
|
|
btn.classList.add('btn-primary');
|
|
}, 3000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
btn.innerHTML = originalContent;
|
|
btn.disabled = false;
|
|
alert('Error de conexión');
|
|
});
|
|
}
|
|
|
|
function updateCount() {
|
|
const count = document.querySelectorAll('input[name="selected_feeds"]:checked').length;
|
|
document.getElementById('selected_count').innerText = count;
|
|
}
|
|
|
|
// Update count on individual clicks
|
|
document.addEventListener('change', function (e) {
|
|
if (e.target.name === 'selected_feeds') {
|
|
updateCount();
|
|
}
|
|
});
|
|
|
|
// Mass Update Functions
|
|
function applyGlobalCategory() {
|
|
const val = document.getElementById('global_categoria_id').value;
|
|
document.querySelectorAll('.item-category-select').forEach(el => el.value = val);
|
|
}
|
|
|
|
function applyGlobalCountry() {
|
|
const val = document.getElementById('global_pais_id').value;
|
|
document.querySelectorAll('.item-country-select').forEach(el => el.value = val);
|
|
}
|
|
|
|
function applyGlobalLanguage() {
|
|
const val = document.getElementById('global_idioma').value;
|
|
document.querySelectorAll('.item-language-input').forEach(el => el.value = val);
|
|
}
|
|
|
|
// Add hover effect to feed items
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const feedItems = document.querySelectorAll('.feed-discovery-item');
|
|
feedItems.forEach(item => {
|
|
item.addEventListener('mouseenter', function () {
|
|
this.style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
|
|
this.style.transform = 'translateY(-2px)';
|
|
});
|
|
item.addEventListener('mouseleave', function () {
|
|
this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
|
|
this.style.transform = 'translateY(0)';
|
|
});
|
|
});
|
|
updateCount();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.feed-discovery-item input[type="checkbox"] {
|
|
accent-color: var(--accent-color, #4CAF50);
|
|
}
|
|
|
|
.form-control {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 5px 10px;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
{% endblock %} |