openvibers/openvibe.community Commits
Pulse on Events realtime (WS-F task 1): on the first page, public activity from here and the other sites (new pastes, threads, replies, go-lives, blog, wiki and news posts) arrives over events.openvibe.network/realtime/stream and a bar offers to show it; CSP connect-src allows events.openvibe.network
Changes
@@@ -326,3 +326,7 @@ button.rx:hover { border-color: var(--accent); }.search-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 4px 0; font-size: .8rem; color: var(--text-muted); }.search-snippet { margin: 4px 0 0; color: var(--text-secondary); overflow-wrap: anywhere; }.search-snippet mark { background: rgba(var(--accent-rgb), .22); color: inherit; border-radius: 3px; padding: 0 2px; }++++/* Pulse: new activity from Events realtime */++.pulse-new { position: sticky; top: 8px; z-index: 5; display: flex; justify-content: center; margin: 0 0 10px; }++.pulse-new[hidden] { display: none; }@@@ -138,4 +138,23 @@});});}++++ // Pulse follows OpenVibe.Events realtime (roadmap WS-F task 1): on the first page, public activity++ // from here and the other sites arrives over events.openvibe.network/realtime/stream, and a bar++ // offers to show it (the page itself stays server-rendered, so a reload is the whole update).++ var pulse = document.querySelector('[data-pulse-live]');++ if (pulse && typeof EventSource !== 'undefined') {++ var topics = ['community.paste.created', 'community.thread.created', 'community.post.created', 'live.stream.started', 'blog.post.published', 'wiki.page.published', 'news.story.published'];++ var bar = pulse.querySelector('.pulse-new'), show = pulse.querySelector('[data-pulse-show]'), fresh = 0;++ var es = new EventSource('https://events.openvibe.network/realtime/stream?topics=' + encodeURIComponent(topics.join(',')));++ es.onmessage = function (m) {++ var d; try { d = JSON.parse(m.data); } catch (e) { return; }++ if (!d || !d.event || topics.indexOf(d.event.event_type) < 0) return;++ fresh++;++ show.textContent = fresh === 1 ? 'Show 1 new item' : 'Show ' + fresh + ' new items';++ bar.hidden = false;++ };++ show.addEventListener('click', function () { es.close(); location.reload(); });++ window.addEventListener('pagehide', function () { es.close(); });++ }})();@@@ -84,7 +84,7 @@ function createApp(opts = {}) {fontSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com', 'data:'],// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.imgSrc: ["'self'", 'data:', 'blob:', 'https:'],-- connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media'],++ connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media', 'https://events.openvibe.network'],// The Network's hidden /sso/check frame: how a visitor who is signed in elsewhere gets signed in here.frameSrc: ["'self'", 'https://openvibe.network'],frameAncestors: ["'self'"],@@@ -47,7 +47,8 @@ function pulsePage({ items, origin = '', after = null, nextCursor = null }) {<p class="muted">Public activity from across OpenVibe as it happens: new pastes, threads and replies here, and what the other sites share. Only public things appear, and anything made by AI is labelled as AI.</p></header><nav class="tabs sort-tabs" aria-label="Filter by origin">${tabs}</nav>--<section data-results>++<section data-results${after ? '' : ' data-pulse-live'}>++ <p class="pulse-new" hidden><button type="button" class="btn btn-sm" data-pulse-show></button></p>${items.length ? `<ol class="thread-list pulse-list">${items.map(itemHtml).join('')}</ol>` : '<p class="empty">Nothing here yet.</p>'}${after || nextCursor ? `<nav class="pager" aria-label="Pages">${after ? `<a href="${esc(href(origin))}"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i> Latest</a>` : '<span></span>'}<span></span>${nextCursor ? `<a rel="next" href="${esc(href(origin, nextCursor))}">Older <i class="fa-solid fa-chevron-right" aria-hidden="true"></i></a>` : '<span></span>'}</nav>` : ''}</section>`;
@@ -326,3 +326,7 @@ button.rx:hover { border-color: var(--accent); }
@@ -326,3 +326,7 @@ button.rx:hover { border-color: var(--accent); }
.search-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 4px 0; font-size: .8rem; color: var(--text-muted); }
.search-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 4px 0; font-size: .8rem; color: var(--text-muted); }
.search-snippet { margin: 4px 0 0; color: var(--text-secondary); overflow-wrap: anywhere; }
.search-snippet { margin: 4px 0 0; color: var(--text-secondary); overflow-wrap: anywhere; }
.search-snippet mark { background: rgba(var(--accent-rgb), .22); color: inherit; border-radius: 3px; padding: 0 2px; }
.search-snippet mark { background: rgba(var(--accent-rgb), .22); color: inherit; border-radius: 3px; padding: 0 2px; }
+
+/* Pulse: new activity from Events realtime */
+.pulse-new { position: sticky; top: 8px; z-index: 5; display: flex; justify-content: center; margin: 0 0 10px; }
+.pulse-new[hidden] { display: none; }
@@ -138,4 +138,23 @@
@@ -138,4 +138,23 @@
});
});
});
});
}
}
+
+ // Pulse follows OpenVibe.Events realtime (roadmap WS-F task 1): on the first page, public activity
+ // from here and the other sites arrives over events.openvibe.network/realtime/stream, and a bar
+ // offers to show it (the page itself stays server-rendered, so a reload is the whole update).
+ var pulse = document.querySelector('[data-pulse-live]');
+ if (pulse && typeof EventSource !== 'undefined') {
+ var topics = ['community.paste.created', 'community.thread.created', 'community.post.created', 'live.stream.started', 'blog.post.published', 'wiki.page.published', 'news.story.published'];
+ var bar = pulse.querySelector('.pulse-new'), show = pulse.querySelector('[data-pulse-show]'), fresh = 0;
+ var es = new EventSource('https://events.openvibe.network/realtime/stream?topics=' + encodeURIComponent(topics.join(',')));
+ es.onmessage = function (m) {
+ var d; try { d = JSON.parse(m.data); } catch (e) { return; }
+ if (!d || !d.event || topics.indexOf(d.event.event_type) < 0) return;
+ fresh++;
+ show.textContent = fresh === 1 ? 'Show 1 new item' : 'Show ' + fresh + ' new items';
+ bar.hidden = false;
+ };
+ show.addEventListener('click', function () { es.close(); location.reload(); });
+ window.addEventListener('pagehide', function () { es.close(); });
+ }
})();
})();
@@ -84,7 +84,7 @@ function createApp(opts = {}) {
@@ -84,7 +84,7 @@ function createApp(opts = {}) {
fontSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com', 'data:'],
fontSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com', 'data:'],
// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.
// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
- connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media'],
+ connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media', 'https://events.openvibe.network'],
// The Network's hidden /sso/check frame: how a visitor who is signed in elsewhere gets signed in here.
// The Network's hidden /sso/check frame: how a visitor who is signed in elsewhere gets signed in here.
frameSrc: ["'self'", 'https://openvibe.network'],
frameSrc: ["'self'", 'https://openvibe.network'],
frameAncestors: ["'self'"],
frameAncestors: ["'self'"],
@@ -47,7 +47,8 @@ function pulsePage({ items, origin = '', after = null, nextCursor = null }) {
@@ -47,7 +47,8 @@ function pulsePage({ items, origin = '', after = null, nextCursor = null }) {
<p class="muted">Public activity from across OpenVibe as it happens: new pastes, threads and replies here, and what the other sites share. Only public things appear, and anything made by AI is labelled as AI.</p>
<p class="muted">Public activity from across OpenVibe as it happens: new pastes, threads and replies here, and what the other sites share. Only public things appear, and anything made by AI is labelled as AI.</p>
</header>
</header>
<nav class="tabs sort-tabs" aria-label="Filter by origin">${tabs}</nav>
<nav class="tabs sort-tabs" aria-label="Filter by origin">${tabs}</nav>
-<section data-results>
+<section data-results${after ? '' : ' data-pulse-live'}>
+ <p class="pulse-new" hidden><button type="button" class="btn btn-sm" data-pulse-show></button></p>
${items.length ? `<ol class="thread-list pulse-list">${items.map(itemHtml).join('')}</ol>` : '<p class="empty">Nothing here yet.</p>'}
${items.length ? `<ol class="thread-list pulse-list">${items.map(itemHtml).join('')}</ol>` : '<p class="empty">Nothing here yet.</p>'}
${after || nextCursor ? `<nav class="pager" aria-label="Pages">${after ? `<a href="${esc(href(origin))}"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i> Latest</a>` : '<span></span>'}<span></span>${nextCursor ? `<a rel="next" href="${esc(href(origin, nextCursor))}">Older <i class="fa-solid fa-chevron-right" aria-hidden="true"></i></a>` : '<span></span>'}</nav>` : ''}
${after || nextCursor ? `<nav class="pager" aria-label="Pages">${after ? `<a href="${esc(href(origin))}"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i> Latest</a>` : '<span></span>'}<span></span>${nextCursor ? `<a rel="next" href="${esc(href(origin, nextCursor))}">Older <i class="fa-solid fa-chevron-right" aria-hidden="true"></i></a>` : '<span></span>'}</nav>` : ''}
</section>`;
</section>`;