From 549bfed7dda8d75d28582bf89ff92407de34a3fd Mon Sep 17 00:00:00 2001 From: OFF0 Date: Sun, 13 Nov 2022 13:15:56 +0100 Subject: [PATCH] feed: render textnotes from multiple relays in chronological order Events should be ordered by created_at. The randomly incoming events should be injected to their closest event sibling. Keeping a map of eventIds and dom nodes to reference and inject the new events. An alternative to keeping a map could be to add a data-attribute to each event and use querySelector. It is a trade-off os using more memory in order to not query the dom tree. --- src/main.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/main.js b/src/main.js index 29fa6cc..89c837f 100644 --- a/src/main.js +++ b/src/main.js @@ -26,10 +26,10 @@ function onEvent(evt, relay) { handleMetadata(evt, relay); break; case 1: - renderTextNote(evt, relay); + handleTextNote(evt, relay); break; case 2: - renderRecommendServer(evt, relay); + feedContainer.prepend(renderRecommendServer(evt, relay)); break; case 3: updateContactList(evt, relay); @@ -57,8 +57,57 @@ const subscription = pool.sub({ } }); -// feed +const textNoteList = []; +const replyList = []; +const eventRelayMap = {}; const hasEventTag = tag => tag[0] === 'e'; + +let debounceRenderFeedTimer; +function handleTextNote(evt, relay) { + if (eventRelayMap[evt.id]) { + eventRelayMap[evt.id] = [relay, ...(eventRelayMap[evt.id])]; + } else { + eventRelayMap[evt.id] = [relay]; + (evt.tags.some(hasEventTag) ? replyList : textNoteList).push(evt); + clearTimeout(debounceRenderFeedTimer); + debounceRenderFeedTimer = setTimeout(renderFeed, 200); + } +} + +// feed +const feedContainer = document.querySelector('#feed'); +const feedDomMap = {}; + +let debounceDebugMessageTimer; +function renderFeed() { + const sortedFeeds = textNoteList.sort((evt1, evt2) => { + if (evt1.created_at === evt2.created_at) { + console.log('OMG exactly at the same time', evt1, evt2); + } + return evt1.created_at > evt2.created_at ? -1 : 1; + }); + + clearTimeout(debounceDebugMessageTimer); + debounceDebugMessageTimer = setTimeout(() => { + console.log(`${sortedFeeds.map(e => dateTime.format(e.created_at * 1000)).join('\n')}`) + }, 1000); + sortedFeeds.forEach((textNoteEvent, i) => { + if (feedDomMap[textNoteEvent.id]) { + // TODO check eventRelayMap if event was published to different relays + return; + } + const art = renderTextNote(textNoteEvent, eventRelayMap[textNoteEvent.id]); + feedDomMap[textNoteEvent.id] = art; + if (i === 0) { + feedContainer.prepend(art); + } else { + feedDomMap[sortedFeeds[i - 1].id].after(art); + // feedContainer.insertBefore(art, feedDomMap[sortedFeeds[i - 1].id]); + //feedDomMap[sortedFeeds[i - 1].id].after(art); + } + }); +} + const getShortTagId = tag => `${tag[1].slice(0, 7)}${tag[2] ? '@' + tag[2] : ''}`; function renderTextNote(evt, relay) { @@ -72,7 +121,7 @@ function renderTextNote(evt, relay) { ]), evt.content, // text ]); - rendernArticle([img, body], isReply && {className: 'mbox-reply'}); + return rendernArticle([img, body], isReply && {className: 'mbox-reply'}); } function renderRecommendServer(evt, relay) { @@ -81,7 +130,7 @@ function renderRecommendServer(evt, relay) { renderProfile(userName, host), `recommends server: ${evt.content}` ]); - rendernArticle([img, body], {className: 'mbox-recommend-server'}); + return rendernArticle([img, body], {className: 'mbox-recommend-server'}); } function renderProfile(userName, host) { @@ -90,12 +139,10 @@ function renderProfile(userName, host) { ]); } -const feedContainer = document.querySelector('#feed'); function rendernArticle(content, props) { const className = ['mbox', props?.className].join(' '); - const art = elem('article', {...props, className}, content); - feedContainer.prepend(art); + return elem('article', {...props, className}, content); } const userList = [];