|
37 | 37 | var editingCategoryId = null; |
38 | 38 | var boardEventContext = null; |
39 | 39 | var embedReady = false; |
| 40 | + var subscribeState = { phase: 'idle' }; |
| 41 | + var previewMode = null; |
40 | 42 |
|
41 | 43 | var $ = function (id) { return document.getElementById(id); }; |
42 | 44 |
|
|
269 | 271 | return d; |
270 | 272 | } |
271 | 273 |
|
| 274 | + async function fetchOteDocument(url) { |
| 275 | + var response = await fetch(url, { headers: { accept: 'application/json,text/html;q=0.9,*/*;q=0.5' } }); |
| 276 | + if (!response.ok) throw new Error('HTTP ' + response.status); |
| 277 | + var type = response.headers.get('content-type') || ''; |
| 278 | + if (type.indexOf('html') !== -1) { |
| 279 | + var html = await response.text(); |
| 280 | + var discovered = discoverInHtml(html, url); |
| 281 | + if (!discovered.length) throw new Error('No encontré cabecera OTE en esa página'); |
| 282 | + return fetchOteDocument(discovered[0].url); |
| 283 | + } |
| 284 | + var raw = await response.json(); |
| 285 | + return { url: url, doc: normaliseDocument(raw, url) }; |
| 286 | + } |
| 287 | + |
272 | 288 | async function loadFeed(feed) { |
273 | 289 | feed.status = 'loading'; |
274 | 290 | renderLibrary(); |
275 | 291 | try { |
276 | | - var response = await fetch(feed.url, { headers: { accept: 'application/json,text/html;q=0.9,*/*;q=0.5' } }); |
277 | | - if (!response.ok) throw new Error('HTTP ' + response.status); |
278 | | - var type = response.headers.get('content-type') || ''; |
279 | | - var doc; |
280 | | - if (type.indexOf('html') !== -1) { |
281 | | - var html = await response.text(); |
282 | | - var discovered = discoverInHtml(html, feed.url); |
283 | | - if (!discovered.length) throw new Error('No encontré cabecera OTE en esa página'); |
284 | | - feed.url = discovered[0].url; |
285 | | - return loadFeed(feed); |
286 | | - } |
287 | | - doc = await response.json(); |
288 | | - var normalised = normaliseDocument(doc, feed.url); |
289 | | - feed.title = feed.customTitle || normalised.title; |
| 292 | + var result = await fetchOteDocument(feed.url); |
| 293 | + feed.url = result.url; |
| 294 | + feed.title = feed.customTitle || result.doc.title; |
290 | 295 | feed.status = 'ok'; |
291 | | - feed.updatedAt = normalised.updatedAt || ''; |
292 | | - feedCache.set(feed.url, normalised); |
| 296 | + feed.updatedAt = result.doc.updatedAt || ''; |
| 297 | + feedCache.set(feed.url, result.doc); |
293 | 298 | } catch (error) { |
294 | 299 | feed.status = 'error'; |
295 | 300 | feed.error = error.message || String(error); |
|
340 | 345 | } |
341 | 346 |
|
342 | 347 | function sourceEvents() { |
| 348 | + if (previewMode) { |
| 349 | + return (previewMode.doc.events || []).map(function (event) { return inherit(previewMode.doc, event); }); |
| 350 | + } |
343 | 351 | var events = allEvents(); |
344 | 352 | if (!state.activeSource || state.activeSource.type === 'all') return events; |
345 | 353 | if (state.activeSource.type === 'feed') { |
|
571 | 579 | } |
572 | 580 |
|
573 | 581 | function setActiveSource(type, id) { |
| 582 | + if (previewMode) { |
| 583 | + previewMode = null; |
| 584 | + updatePreviewControls(); |
| 585 | + } |
574 | 586 | state.activeSource = { type: type, id: id }; |
575 | 587 | persist(); |
576 | 588 | render(); |
577 | 589 | } |
578 | 590 |
|
579 | 591 | function isActiveSource(type, id) { |
| 592 | + if (previewMode) return false; |
580 | 593 | return state.activeSource && state.activeSource.type === type && state.activeSource.id === id; |
581 | 594 | } |
582 | 595 |
|
|
1090 | 1103 | if (!events.length) { |
1091 | 1104 | widget.events = []; |
1092 | 1105 | widget.setAttribute('empty-message', 'No hay eventos con esos filtros.'); |
1093 | | - if (!state.feeds.length) showEmptyState(); |
| 1106 | + if (previewMode) showMessage('Este feed no tiene eventos.', 'warn', true); |
| 1107 | + else if (!state.feeds.length) showEmptyState(); |
1094 | 1108 | else showMessage('No hay eventos con esos filtros.', 'warn', true); |
1095 | 1109 | return; |
1096 | 1110 | } |
|
1110 | 1124 | widget.eventActions = function (context) { |
1111 | 1125 | var event = context.originalEvent; |
1112 | 1126 | if (!event) return []; |
| 1127 | + if (previewMode) { |
| 1128 | + return [ |
| 1129 | + { type: 'link', placement: 'detail' }, |
| 1130 | + { type: 'ics', placement: 'detail' } |
| 1131 | + ]; |
| 1132 | + } |
1113 | 1133 | var saved = state.boards.some(function (board) { return boardHasEvent(board, event); }); |
1114 | 1134 | return [ |
1115 | 1135 | { |
|
1394 | 1414 | loadFeed(feed); |
1395 | 1415 | } |
1396 | 1416 |
|
| 1417 | + function setSubscribeState(next) { |
| 1418 | + subscribeState = next; |
| 1419 | + renderSubscribeModal(); |
| 1420 | + } |
| 1421 | + |
| 1422 | + function resetSubscribeModal() { |
| 1423 | + $('feed-url').value = ''; |
| 1424 | + setSubscribeState({ phase: 'idle' }); |
| 1425 | + } |
| 1426 | + |
| 1427 | + function renderSubscribeModal() { |
| 1428 | + var box = $('subscribe-preview'); |
| 1429 | + var previewButton = $('subscribe-preview-button'); |
| 1430 | + box.replaceChildren(); |
| 1431 | + previewButton.disabled = subscribeState.phase === 'loading'; |
| 1432 | + if (subscribeState.phase === 'idle') { |
| 1433 | + box.hidden = true; |
| 1434 | + return; |
| 1435 | + } |
| 1436 | + box.hidden = false; |
| 1437 | + if (subscribeState.phase === 'loading') { |
| 1438 | + box.appendChild(messageNode('Cargando...')); |
| 1439 | + return; |
| 1440 | + } |
| 1441 | + if (subscribeState.phase === 'error') { |
| 1442 | + box.appendChild(messageNode(subscribeState.message, 'warn')); |
| 1443 | + } |
| 1444 | + } |
| 1445 | + |
| 1446 | + async function handleSubscribePreview() { |
| 1447 | + var raw = $('feed-url').value; |
| 1448 | + if (!raw.trim()) return; |
| 1449 | + var url; |
| 1450 | + try { url = normaliseUrl(raw); } catch (e) { |
| 1451 | + setSubscribeState({ phase: 'error', message: 'La URL no parece válida.' }); |
| 1452 | + return; |
| 1453 | + } |
| 1454 | + var existing = findFeed(url); |
| 1455 | + if (existing) { |
| 1456 | + closeModal('subscribe-modal'); |
| 1457 | + loadFeed(existing); |
| 1458 | + setActiveSource('feed', existing.url); |
| 1459 | + showMessage('Ya estabas suscrito a esta fuente. Actualizando...', 'warn', true); |
| 1460 | + return; |
| 1461 | + } |
| 1462 | + setSubscribeState({ phase: 'loading' }); |
| 1463 | + try { |
| 1464 | + var result = await fetchOteDocument(url); |
| 1465 | + closeModal('subscribe-modal'); |
| 1466 | + enterPreviewMode(result.url, result.doc); |
| 1467 | + } catch (error) { |
| 1468 | + setSubscribeState({ phase: 'error', message: error.message || String(error) }); |
| 1469 | + } |
| 1470 | + } |
| 1471 | + |
| 1472 | + function enterPreviewMode(url, doc) { |
| 1473 | + previewMode = { url: url, doc: doc }; |
| 1474 | + updatePreviewControls(); |
| 1475 | + render(); |
| 1476 | + } |
| 1477 | + |
| 1478 | + function cancelPreview() { |
| 1479 | + if (!previewMode) return; |
| 1480 | + previewMode = null; |
| 1481 | + updatePreviewControls(); |
| 1482 | + render(); |
| 1483 | + } |
| 1484 | + |
| 1485 | + function confirmPreviewSubscription() { |
| 1486 | + if (!previewMode) return; |
| 1487 | + confirmSubscription(previewMode.url, previewMode.doc); |
| 1488 | + setActiveSource('feed', previewMode.url); |
| 1489 | + } |
| 1490 | + |
| 1491 | + function updatePreviewControls() { |
| 1492 | + $('mark-all-read').hidden = Boolean(previewMode); |
| 1493 | + $('refresh').hidden = Boolean(previewMode); |
| 1494 | + $('preview-confirm-button').hidden = !previewMode; |
| 1495 | + $('preview-cancel-button').hidden = !previewMode; |
| 1496 | + } |
| 1497 | + |
| 1498 | + function confirmSubscription(url, doc) { |
| 1499 | + var feed = { url: url, status: 'ok', title: doc.title, updatedAt: doc.updatedAt || '' }; |
| 1500 | + state.feeds.push(feed); |
| 1501 | + ensureDefaultCategory(feed.url); |
| 1502 | + feedCache.set(feed.url, doc); |
| 1503 | + persist(); |
| 1504 | + render(); |
| 1505 | + } |
| 1506 | + |
1397 | 1507 | function unsubscribe(rawUrl) { |
1398 | 1508 | var url = normaliseUrl(rawUrl); |
1399 | 1509 | state.feeds = state.feeds.filter(function (feed) { return feed.url !== url; }); |
|
1413 | 1523 | } |
1414 | 1524 |
|
1415 | 1525 | function sourceLabel() { |
| 1526 | + if (previewMode) return previewMode.doc.title || 'Previsualización'; |
1416 | 1527 | if (!state.activeSource || state.activeSource.type === 'all') return 'All'; |
1417 | 1528 | if (state.activeSource.type === 'feed') { |
1418 | 1529 | var feed = findFeed(state.activeSource.id); |
|
1439 | 1550 | var modal = $(id); |
1440 | 1551 | if (modal.close) modal.close(); |
1441 | 1552 | else modal.removeAttribute('open'); |
| 1553 | + if (id === 'subscribe-modal') resetSubscribeModal(); |
1442 | 1554 | } |
1443 | 1555 |
|
1444 | 1556 | function bind() { |
|
1469 | 1581 | }); |
1470 | 1582 | $('subscribe-form').addEventListener('submit', function (event) { |
1471 | 1583 | event.preventDefault(); |
1472 | | - subscribe($('feed-url').value); |
1473 | | - $('feed-url').value = ''; |
1474 | | - closeModal('subscribe-modal'); |
| 1584 | + handleSubscribePreview(); |
| 1585 | + }); |
| 1586 | + $('feed-url').addEventListener('input', function () { |
| 1587 | + if (subscribeState.phase !== 'idle') setSubscribeState({ phase: 'idle' }); |
1475 | 1588 | }); |
| 1589 | + $('subscribe-modal').addEventListener('close', resetSubscribeModal); |
| 1590 | + $('preview-confirm-button').addEventListener('click', confirmPreviewSubscription); |
| 1591 | + $('preview-cancel-button').addEventListener('click', cancelPreview); |
1476 | 1592 | ['q', 'mode', 'language', 'country', 'cfp', 'future'].forEach(function (id) { |
1477 | 1593 | $(id).addEventListener('input', function () { |
1478 | 1594 | state.filters[id] = this.type === 'checkbox' ? this.checked : (id === 'q' ? normaliseQuery(this.value) : this.value); |
|
0 commit comments