|
34 | 34 | var optionsContext = null; |
35 | 35 | var editingCategoryId = null; |
36 | 36 | var boardEventContext = null; |
| 37 | + var embedReady = false; |
37 | 38 |
|
38 | 39 | var $ = function (id) { return document.getElementById(id); }; |
39 | 40 |
|
|
46 | 47 |
|
47 | 48 | function applyTheme(theme) { |
48 | 49 | document.documentElement.dataset.theme = theme; |
| 50 | + if ($('event-list')) $('event-list').setAttribute('theme', theme); |
49 | 51 | var dark = theme === 'dark'; |
50 | 52 | var toggle = $('theme-toggle'); |
51 | 53 | if (toggle) { |
|
1079 | 1081 | } |
1080 | 1082 |
|
1081 | 1083 | function renderEvents() { |
1082 | | - var list = $('event-list'); |
| 1084 | + var widget = $('event-list'); |
1083 | 1085 | var events = filteredEvents(); |
1084 | | - list.className = 'events view-' + state.view; |
1085 | | - list.replaceChildren(); |
1086 | 1086 | $('result-count').textContent = sourceLabel() + ' · ' + events.length + (events.length === 1 ? ' evento' : ' eventos'); |
1087 | 1087 | if (!events.length) { |
| 1088 | + widget.events = []; |
| 1089 | + widget.setAttribute('empty-message', 'No hay eventos con esos filtros.'); |
1088 | 1090 | showMessage('No hay eventos con esos filtros.', 'warn', true); |
1089 | 1091 | return; |
1090 | 1092 | } |
1091 | 1093 | clearMessages(); |
1092 | | - if (state.view === 'calendar') renderCalendar(events, list); |
1093 | | - else events.forEach(function (event) { list.appendChild(renderEvent(event)); }); |
1094 | | - } |
1095 | | - |
1096 | | - function renderCalendar(events, list) { |
1097 | | - var groups = {}; |
1098 | | - events.forEach(function (event) { |
1099 | | - var date = startAsDate(event); |
1100 | | - var key = date ? date.toLocaleDateString('es', { month: 'long', year: 'numeric' }) : 'Sin fecha'; |
1101 | | - if (!groups[key]) groups[key] = []; |
1102 | | - groups[key].push(event); |
1103 | | - }); |
1104 | | - Object.keys(groups).forEach(function (key) { |
1105 | | - var group = document.createElement('section'); |
1106 | | - group.className = 'calendar-group'; |
1107 | | - var title = document.createElement('h3'); |
1108 | | - title.textContent = key; |
1109 | | - group.appendChild(title); |
1110 | | - groups[key].forEach(function (event) { |
1111 | | - group.appendChild(renderEvent(event)); |
1112 | | - }); |
1113 | | - list.appendChild(group); |
1114 | | - }); |
1115 | | - } |
1116 | | - |
1117 | | - function renderEvent(event) { |
1118 | | - var tpl = $('event-template').content.cloneNode(true); |
1119 | | - var card = tpl.querySelector('.event-card'); |
1120 | | - if (event.status === 'cancelled') card.classList.add('is-cancelled'); |
1121 | | - var date = startAsDate(event); |
1122 | | - tpl.querySelector('.event-date').innerHTML = date |
1123 | | - ? '<span>' + date.toLocaleString('es', { month: 'short' }) + '</span>' + date.getDate() |
1124 | | - : '<span>sin</span>fecha'; |
1125 | | - tpl.querySelector('.event-topline').textContent = state.view === 'feed' |
1126 | | - ? 'Actualizado ' + formatUpdated(event) + ' · ' + event._feedTitle |
1127 | | - : [event._feedTitle, organizerText(event)].filter(Boolean).join(' · '); |
1128 | | - tpl.querySelector('h3').textContent = event.name; |
1129 | | - tpl.querySelector('.event-description').appendChild(markdown(event.description || 'Sin descripción en el feed.')); |
1130 | | - tpl.querySelector('.event-meta').textContent = [ |
1131 | | - formatDate(event), |
1132 | | - labelMode(event.attendanceMode), |
1133 | | - locationText(event), |
1134 | | - (event.languages || []).join(', ') |
1135 | | - ].filter(Boolean).join(' · '); |
1136 | | - var tags = tpl.querySelector('.event-tags'); |
1137 | | - (event.tags || []).forEach(function (tag) { |
1138 | | - var item = document.createElement('span'); |
1139 | | - item.className = 'tag'; |
1140 | | - item.textContent = '#' + tag; |
1141 | | - tags.appendChild(item); |
1142 | | - }); |
1143 | | - if (cfpIsOpen(event)) tags.appendChild(pill('CFP abierto', 'ok')); |
1144 | | - if (event.status === 'cancelled') tags.appendChild(pill('Cancelado', 'warn')); |
1145 | | - var link = tpl.querySelector('.primary-link'); |
1146 | | - link.href = event.url || event.cfp && event.cfp.url || event._feedUrl || event.id; |
1147 | | - var save = tpl.querySelector('.save-event'); |
1148 | | - var saved = state.boards.some(function (board) { return boardHasEvent(board, event); }); |
1149 | | - save.textContent = saved ? 'En colección' : 'Favorito'; |
1150 | | - save.classList.toggle('is-active', saved); |
1151 | | - save.setAttribute('aria-pressed', String(saved)); |
1152 | | - save.addEventListener('click', function () { |
1153 | | - openBoardPicker(event); |
1154 | | - }); |
1155 | | - tpl.querySelector('.calendar-event').addEventListener('click', function () { |
1156 | | - downloadIcs(event); |
1157 | | - }); |
1158 | | - return tpl; |
| 1094 | + if (!embedReady) { |
| 1095 | + showMessage('El visor de eventos se está cargando...', 'warn', true); |
| 1096 | + return; |
| 1097 | + } |
| 1098 | + widget.setAttribute('layout', state.view === 'feed' ? 'list' : state.view); |
| 1099 | + widget.setAttribute('theme', document.documentElement.dataset.theme || 'light'); |
| 1100 | + widget.setAttribute('fields', 'image,when,location,attendance,description,price,tags,organizer'); |
| 1101 | + widget.setAttribute('show-past', 'true'); |
| 1102 | + widget.setAttribute('sort', 'none'); |
| 1103 | + widget.setAttribute('event-actions', 'none'); |
| 1104 | + widget.setAttribute('empty-message', 'No hay eventos con esos filtros.'); |
| 1105 | + widget.events = events; |
| 1106 | + widget.eventActions = function (context) { |
| 1107 | + var event = context.originalEvent; |
| 1108 | + if (!event) return []; |
| 1109 | + var saved = state.boards.some(function (board) { return boardHasEvent(board, event); }); |
| 1110 | + return [ |
| 1111 | + { |
| 1112 | + id: 'save-to-collection', |
| 1113 | + label: saved ? 'En colección' : 'Guardar', |
| 1114 | + icon: saved ? 'bookmark' : 'star', |
| 1115 | + pressed: saved, |
| 1116 | + placement: 'both', |
| 1117 | + onClick: function (_previewEvent, actionContext) { |
| 1118 | + if (actionContext.originalEvent) openBoardPicker(actionContext.originalEvent); |
| 1119 | + } |
| 1120 | + }, |
| 1121 | + { type: 'link', placement: 'detail' }, |
| 1122 | + { type: 'ics', placement: 'detail' } |
| 1123 | + ]; |
| 1124 | + }; |
| 1125 | + widget.eventClassName = function (context) { |
| 1126 | + var event = context.originalEvent; |
| 1127 | + return [ |
| 1128 | + event && event.status === 'cancelled' ? 'reader-cancelled' : '', |
| 1129 | + event && eventIsUnread(event) ? 'reader-unread' : 'reader-read' |
| 1130 | + ].filter(Boolean); |
| 1131 | + }; |
| 1132 | + widget.eventBadges = function (context) { |
| 1133 | + var event = context.originalEvent; |
| 1134 | + if (!event) return []; |
| 1135 | + var badges = []; |
| 1136 | + if (event._feedTitle) badges.push({ label: event._feedTitle, icon: 'folder' }); |
| 1137 | + if (cfpIsOpen(event)) badges.push({ label: 'CFP abierto', icon: 'check', tone: 'success' }); |
| 1138 | + if (event.status === 'cancelled') badges.push({ label: 'Cancelado', tone: 'warning' }); |
| 1139 | + return badges; |
| 1140 | + }; |
| 1141 | + } |
| 1142 | + |
| 1143 | + function eventIsUnread(event) { |
| 1144 | + var feed = event && findFeed(event._feedUrl); |
| 1145 | + if (!feed || feed.status !== 'ok') return false; |
| 1146 | + if (!feed.lastSeenAt) return true; |
| 1147 | + var activity = event.updatedAt ? new Date(event.updatedAt) : null; |
| 1148 | + return Boolean(activity && !Number.isNaN(activity.getTime()) && activity > new Date(feed.lastSeenAt)); |
1159 | 1149 | } |
1160 | 1150 |
|
1161 | 1151 | function openBoardPicker(event) { |
|
1638 | 1628 | loadState(); |
1639 | 1629 | bind(); |
1640 | 1630 | updateInstallUi(); |
| 1631 | + await waitForEmbed(); |
1641 | 1632 | render(); |
1642 | 1633 | applySubscribeParam(); |
1643 | 1634 | await Promise.all(state.feeds.map(loadFeed)); |
|
1646 | 1637 | } |
1647 | 1638 | } |
1648 | 1639 |
|
| 1640 | + async function waitForEmbed() { |
| 1641 | + if (!window.customElements || !customElements.whenDefined) return; |
| 1642 | + try { |
| 1643 | + await Promise.race([ |
| 1644 | + customElements.whenDefined('ote-events'), |
| 1645 | + new Promise(function (_, reject) { |
| 1646 | + setTimeout(function () { reject(new Error('timeout')); }, 6000); |
| 1647 | + }) |
| 1648 | + ]); |
| 1649 | + embedReady = true; |
| 1650 | + } catch (e) { |
| 1651 | + embedReady = false; |
| 1652 | + showMessage('No se pudo cargar el componente de visualización OTE.', 'warn', true); |
| 1653 | + } |
| 1654 | + } |
| 1655 | + |
1649 | 1656 | boot(); |
1650 | 1657 | }()); |
0 commit comments