|
|
|
@ -4,13 +4,13 @@ import {elem} from './utils/dom';
|
|
|
|
|
import {bounce} from './utils/time';
|
|
|
|
|
import {isWssUrl} from './utils/url';
|
|
|
|
|
import {closeSettingsView, config, toggleSettingsView} from './settings';
|
|
|
|
|
import {sub24hFeed, subEventID, subNote, subProfile} from './subscriptions'
|
|
|
|
|
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt} from './events';
|
|
|
|
|
import {subGlobalFeed, subEventID, subNote, subProfile, subPubkeys, subOwnContacts} from './subscriptions'
|
|
|
|
|
import {getReplyTo, hasEventTag, isEvent, isMention, sortByCreatedAt, sortEventCreatedAt} from './events';
|
|
|
|
|
import {clearView, getViewContent, getViewElem, getViewOptions, setViewElem, view} from './view';
|
|
|
|
|
import {handleReaction, handleUpvote} from './reactions';
|
|
|
|
|
import {closePublishView, openWriteInput, togglePublishView} from './write';
|
|
|
|
|
import {handleMetadata, renderProfile} from './profiles';
|
|
|
|
|
import {followContact, getContactUpdateMessage, setContactList, updateContactList} from './contacts';
|
|
|
|
|
import {followContact, getContactUpdateMessage, getContacts, resetContactList, setContactList, updateContactList, updateFollowBtn} from './contacts';
|
|
|
|
|
import {EventWithNip19, EventWithNip19AndReplyTo, textNoteList, replyList} from './notes';
|
|
|
|
|
import {createTextNote, renderEventDetails, renderRecommendServer, renderUpdateContact} from './ui';
|
|
|
|
|
|
|
|
|
@ -55,7 +55,6 @@ const renderFeed = bounce(() => {
|
|
|
|
|
.forEach(renderNote);
|
|
|
|
|
break;
|
|
|
|
|
case 'profile':
|
|
|
|
|
const isEvent = <T>(evt?: T): evt is T => evt !== undefined;
|
|
|
|
|
[
|
|
|
|
|
...textNoteList // get notes
|
|
|
|
|
.filter(note => note.pubkey === view.id),
|
|
|
|
@ -69,6 +68,20 @@ const renderFeed = bounce(() => {
|
|
|
|
|
|
|
|
|
|
renderProfile(view.id);
|
|
|
|
|
break;
|
|
|
|
|
case 'home':
|
|
|
|
|
const ids = getContacts();
|
|
|
|
|
[
|
|
|
|
|
...textNoteList
|
|
|
|
|
.filter(note => ids.includes(note.pubkey)),
|
|
|
|
|
...replyList // search id in notes and replies
|
|
|
|
|
.filter(reply => ids.includes(reply.pubkey))
|
|
|
|
|
.map(reply => textNoteList.find(note => note.id === reply.replyTo))
|
|
|
|
|
.filter(isEvent),
|
|
|
|
|
]
|
|
|
|
|
.sort(sortByCreatedAt)
|
|
|
|
|
.reverse()
|
|
|
|
|
.forEach(renderNote);
|
|
|
|
|
break;
|
|
|
|
|
case 'feed':
|
|
|
|
|
const now = Math.floor(Date.now() * 0.001);
|
|
|
|
|
textNoteList
|
|
|
|
@ -87,7 +100,7 @@ const renderFeed = bounce(() => {
|
|
|
|
|
|
|
|
|
|
const renderReply = (evt: EventWithNip19AndReplyTo) => {
|
|
|
|
|
const parent = getViewElem(evt.replyTo);
|
|
|
|
|
if (!parent) { // root article has not been rendered
|
|
|
|
|
if (!parent || getViewElem(evt.id)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let replyContainer = parent.querySelector('.mbox-replies');
|
|
|
|
@ -110,7 +123,6 @@ const handleReply = (evt: EventWithNip19, relay: string) => {
|
|
|
|
|
}
|
|
|
|
|
const replyTo = getReplyTo(evt);
|
|
|
|
|
if (!replyTo) {
|
|
|
|
|
console.warn('expected to find reply-to-event-id', evt);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const evtWithReplyTo = {replyTo, ...evt};
|
|
|
|
@ -124,7 +136,7 @@ const handleTextNote = (evt: Event, relay: string) => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (eventRelayMap[evt.id]) {
|
|
|
|
|
eventRelayMap[evt.id] = [...(eventRelayMap[evt.id]), relay]; // TODO: just push?
|
|
|
|
|
eventRelayMap[evt.id] = [...(eventRelayMap[evt.id]), relay]; // TODO: remove eventRelayMap and just check for getViewElem?
|
|
|
|
|
} else {
|
|
|
|
|
eventRelayMap[evt.id] = [relay];
|
|
|
|
|
const evtWithNip19 = {
|
|
|
|
@ -145,12 +157,14 @@ const handleTextNote = (evt: Event, relay: string) => {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config.rerenderFeed = () => {
|
|
|
|
|
const rerenderFeed = () => {
|
|
|
|
|
clearView();
|
|
|
|
|
renderFeed();
|
|
|
|
|
};
|
|
|
|
|
config.rerenderFeed = rerenderFeed;
|
|
|
|
|
|
|
|
|
|
const handleContactList = (evt: Event, relay: string) => {
|
|
|
|
|
// TODO: if newer and view.type === 'home' rerenderFeed()
|
|
|
|
|
setContactList(evt);
|
|
|
|
|
const view = getViewOptions();
|
|
|
|
|
if (
|
|
|
|
@ -229,9 +243,21 @@ const onEvent = (evt: Event, relay: string) => {
|
|
|
|
|
|
|
|
|
|
// subscribe and change view
|
|
|
|
|
const route = (path: string) => {
|
|
|
|
|
const contactList = getContacts();
|
|
|
|
|
if (path === '/') {
|
|
|
|
|
sub24hFeed(onEvent);
|
|
|
|
|
view('/', {type: 'feed'});
|
|
|
|
|
if (contactList.length) {
|
|
|
|
|
const {pubkey} = config;
|
|
|
|
|
subPubkeys(contactList, onEvent);
|
|
|
|
|
view(`/`, {type: 'home'});
|
|
|
|
|
} else {
|
|
|
|
|
subGlobalFeed(onEvent);
|
|
|
|
|
view('/feed', {type: 'feed'});
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (path === '/feed') {
|
|
|
|
|
subGlobalFeed(onEvent);
|
|
|
|
|
view('/feed', {type: 'feed'});
|
|
|
|
|
} else if (path.length === 64 && path.match(/^\/[0-9a-z]+$/)) {
|
|
|
|
|
const {type, data} = nip19.decode(path.slice(1));
|
|
|
|
|
if (typeof data !== 'string') {
|
|
|
|
@ -246,6 +272,7 @@ const route = (path: string) => {
|
|
|
|
|
case 'npub':
|
|
|
|
|
subProfile(data, onEvent);
|
|
|
|
|
view(path, {type: 'profile', id: data});
|
|
|
|
|
updateFollowBtn(data);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
console.warn(`type ${type} not yet supported`);
|
|
|
|
@ -261,6 +288,7 @@ const route = (path: string) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// onload
|
|
|
|
|
subOwnContacts(onEvent);
|
|
|
|
|
route(location.pathname);
|
|
|
|
|
|
|
|
|
|
// only push a new entry if there is no history onload
|
|
|
|
@ -286,8 +314,10 @@ const handleLink = (a: HTMLAnchorElement, e: MouseEvent) => {
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
href === '/'
|
|
|
|
|
|| href.startsWith('/feed')
|
|
|
|
|
|| href.startsWith('/note')
|
|
|
|
|
|| href.startsWith('/npub')
|
|
|
|
|
|| href.length === 65
|
|
|
|
|
) {
|
|
|
|
|
route(href);
|
|
|
|
|
history.pushState({}, '', href);
|
|
|
|
@ -306,20 +336,26 @@ const handleButton = (button: HTMLButtonElement) => {
|
|
|
|
|
case 'back':
|
|
|
|
|
closePublishView();
|
|
|
|
|
return;
|
|
|
|
|
case 'import':
|
|
|
|
|
resetContactList(config.pubkey);
|
|
|
|
|
rerenderFeed();
|
|
|
|
|
subOwnContacts(onEvent);
|
|
|
|
|
subGlobalFeed(onEvent);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const id = button.dataset.id || (button.closest('[data-id]') as HTMLElement)?.dataset.id;
|
|
|
|
|
if (id) {
|
|
|
|
|
switch(button.name) {
|
|
|
|
|
case 'reply':
|
|
|
|
|
openWriteInput(button, id);
|
|
|
|
|
break;
|
|
|
|
|
return;
|
|
|
|
|
case 'star':
|
|
|
|
|
const note = replyList.find(r => r.id === id) || textNoteList.find(n => n.id === (id));
|
|
|
|
|
note && handleUpvote(note);
|
|
|
|
|
break;
|
|
|
|
|
const note = replyList.find(r => r.id === id) || textNoteList.find(n => n.id === (id));
|
|
|
|
|
note && handleUpvote(note);
|
|
|
|
|
return;
|
|
|
|
|
case 'follow':
|
|
|
|
|
followContact(id);
|
|
|
|
|
break;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// const container = e.target.closest('[data-append]');
|
|
|
|
|