654 lines
23 KiB
JavaScript
654 lines
23 KiB
JavaScript
|
|
import {
|
|
initNDKPage,
|
|
getPubkey,
|
|
injectHeaderAvatar,
|
|
subscribe,
|
|
publishEvent,
|
|
disconnect,
|
|
getVersion,
|
|
updateVersionDisplay,
|
|
queryCache,
|
|
fetchEventsFromAllRelays,
|
|
fetchCachedProfile,
|
|
storeProfile
|
|
} from './js/init-ndk.mjs';
|
|
import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
|
|
import { createProfileCache } from './js/profile-cache.mjs';
|
|
import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
|
|
|
|
import { initBlossomSection, updateBlossomSection } from './js/blossom-ui.mjs';
|
|
const versionInfo = await getVersion();
|
|
const VERSION = versionInfo.VERSION;
|
|
console.log(`[people.html ${VERSION}] Loading...`);
|
|
|
|
let updateIntervalId = null;
|
|
let currentPubkey = null;
|
|
let isNavOpen = false;
|
|
let hamburgerInstance = null;
|
|
let logoutHamburger = null;
|
|
let themeToggleHamburger = null;
|
|
let isDarkMode = false;
|
|
|
|
let followedPubkeys = [];
|
|
let latestContactContent = {};
|
|
const profileCacheApi = createProfileCache({
|
|
fetchCachedProfile,
|
|
fetchEventsFromAllRelays,
|
|
storeProfile,
|
|
queryCache
|
|
});
|
|
const profileRelayRequested = new Set();
|
|
const PROFILE_BATCH_SIZE = 25;
|
|
const DEBUG_PEOPLE = true;
|
|
|
|
function dlog(...args) {
|
|
if (!DEBUG_PEOPLE) return;
|
|
console.log('[people.html]', ...args);
|
|
}
|
|
|
|
const divBody = document.getElementById('divBody');
|
|
const divSideNav = document.getElementById('divSideNav');
|
|
const divSideNavBody = document.getElementById('divSideNavBody');
|
|
const divFooterCenter = document.getElementById('divFooterCenter');
|
|
const divFooterRight = document.getElementById('divFooterRight');
|
|
|
|
const inpSearchPeople = document.getElementById('inpSearchPeople');
|
|
const inpAddPerson = document.getElementById('inpAddPerson');
|
|
const btnAddPerson = document.getElementById('btnAddPerson');
|
|
const divPeopleStatus = document.getElementById('divPeopleStatus');
|
|
const divPeopleGrid = document.getElementById('divPeopleGrid');
|
|
|
|
function setStatus(msg) {
|
|
divPeopleStatus.textContent = msg;
|
|
}
|
|
|
|
function shortPubkey(pk = '') {
|
|
return `${pk.slice(0, 8)}…${pk.slice(-4)}`;
|
|
}
|
|
|
|
function parseFollowedPubkeysFromKind3(evt) {
|
|
const tags = Array.isArray(evt?.tags) ? evt.tags : [];
|
|
return tags
|
|
.filter((t) => Array.isArray(t) && t[0] === 'p' && typeof t[1] === 'string' && t[1].length === 64)
|
|
.map((t) => t[1]);
|
|
}
|
|
|
|
function dedupePubkeys(arr) {
|
|
return [...new Set((arr || []).filter((v) => typeof v === 'string' && v.length === 64))];
|
|
}
|
|
|
|
function normalizePubkeyInput(raw) {
|
|
const value = String(raw || '').trim();
|
|
if (!value) throw new Error('Enter npub or hex pubkey');
|
|
|
|
if (/^[a-f0-9]{64}$/i.test(value)) return value.toLowerCase();
|
|
|
|
if (value.startsWith('npub1')) {
|
|
const decoded = window?.NostrTools?.nip19?.decode?.(value);
|
|
if (decoded?.type === 'npub' && typeof decoded.data === 'string' && decoded.data.length === 64) {
|
|
return decoded.data;
|
|
}
|
|
}
|
|
|
|
throw new Error('Invalid pubkey format');
|
|
}
|
|
|
|
function openNav() {
|
|
divSideNav.style.zIndex = 3;
|
|
divSideNav.style.width = '50vw';
|
|
isNavOpen = true;
|
|
if (hamburgerInstance) hamburgerInstance.animateTo('arrow_left');
|
|
divSideNavBody.innerHTML = 'Settings and options coming soon...';
|
|
|
|
if (!logoutHamburger) {
|
|
logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
logoutHamburger.animateTo('x');
|
|
}
|
|
|
|
if (!themeToggleHamburger) {
|
|
themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
|
|
size: 24,
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
const savedTheme = localStorage.getItem('theme');
|
|
isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
|
|
themeToggleHamburger.animateTo(isDarkMode ? 'moon' : 'circle');
|
|
}
|
|
}
|
|
|
|
function closeNav() {
|
|
divSideNav.style.width = '0vw';
|
|
divSideNav.style.zIndex = -1;
|
|
isNavOpen = false;
|
|
if (hamburgerInstance) hamburgerInstance.animateTo('burger');
|
|
}
|
|
|
|
function toggleNav() {
|
|
if (isNavOpen) closeNav(); else openNav();
|
|
}
|
|
|
|
function initHamburgerMenu() {
|
|
hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
|
|
foreground: 'var(--primary-color)',
|
|
background: 'var(--secondary-color)',
|
|
hover: 'var(--accent-color)'
|
|
});
|
|
hamburgerInstance.animateTo('burger');
|
|
}
|
|
|
|
async function UpdateFooter() {
|
|
try {
|
|
await updateFooterRelayStatus();
|
|
await updateSidenavRelaySection();
|
|
await updateBlossomSection();
|
|
divFooterCenter.innerHTML = '';
|
|
divFooterRight.innerHTML = '';
|
|
} catch (error) {
|
|
console.error('[people.html] Error updating footer:', error);
|
|
}
|
|
}
|
|
|
|
async function Logout() {
|
|
if (updateIntervalId) {
|
|
clearInterval(updateIntervalId);
|
|
updateIntervalId = null;
|
|
}
|
|
disconnect();
|
|
if (window.NOSTR_LOGIN_LITE?.logout) await window.NOSTR_LOGIN_LITE.logout();
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
if (window.indexedDB) {
|
|
const databases = await window.indexedDB.databases();
|
|
for (const db of databases) if (db.name) window.indexedDB.deleteDatabase(db.name);
|
|
}
|
|
location.reload(true);
|
|
}
|
|
|
|
function personMatchesQuery(pubkey, profile, query) {
|
|
const q = String(query || '').trim().toLowerCase();
|
|
if (!q) return true;
|
|
const name = String(profile?.display_name || profile?.name || '').toLowerCase();
|
|
return name.includes(q) || pubkey.toLowerCase().includes(q);
|
|
}
|
|
|
|
function createAvatarElement(pubkey, profile) {
|
|
const wrap = document.createElement('div');
|
|
wrap.className = 'personAvatarWrap';
|
|
const img = document.createElement('img');
|
|
img.className = 'personAvatar';
|
|
img.alt = '';
|
|
img.loading = 'lazy';
|
|
img.decoding = 'async';
|
|
img.referrerPolicy = 'no-referrer';
|
|
|
|
const picture = String(profile?.picture || '').trim();
|
|
if (picture) {
|
|
img.addEventListener('error', () => {
|
|
img.removeAttribute('src');
|
|
}, { once: true });
|
|
img.src = picture;
|
|
}
|
|
|
|
wrap.appendChild(img);
|
|
wrap.addEventListener('click', () => {
|
|
window.location.href = `./post.html?npub=${encodeURIComponent(pubkey)}&profile=true&post=false`;
|
|
});
|
|
return wrap;
|
|
}
|
|
|
|
function getCachedProfile(pubkey) {
|
|
return profileCacheApi.getCachedProfile(pubkey) || null;
|
|
}
|
|
|
|
function getDisplayNameForSort(pubkey) {
|
|
const profile = getCachedProfile(pubkey);
|
|
const name = String(profile?.display_name || profile?.name || '').trim();
|
|
return name;
|
|
}
|
|
|
|
function getOrderedPubkeys() {
|
|
const rows = followedPubkeys.map((pk) => {
|
|
const displayName = getDisplayNameForSort(pk);
|
|
return {
|
|
pubkey: pk,
|
|
hasDisplayName: Boolean(displayName),
|
|
key: (displayName || '').toLowerCase(),
|
|
fallback: pk.toLowerCase()
|
|
};
|
|
});
|
|
|
|
rows.sort((a, b) => {
|
|
if (a.hasDisplayName !== b.hasDisplayName) return a.hasDisplayName ? -1 : 1;
|
|
if (a.key !== b.key) return a.key.localeCompare(b.key);
|
|
return a.fallback.localeCompare(b.fallback);
|
|
});
|
|
|
|
return rows.map((r) => r.pubkey);
|
|
}
|
|
|
|
async function renderPeople() {
|
|
const query = inpSearchPeople.value;
|
|
const ordered = getOrderedPubkeys();
|
|
const visible = ordered.filter((pk) => personMatchesQuery(pk, getCachedProfile(pk), query));
|
|
|
|
divPeopleGrid.innerHTML = '';
|
|
|
|
if (visible.length === 0) {
|
|
const empty = document.createElement('div');
|
|
empty.className = 'personCard';
|
|
empty.textContent = followedPubkeys.length === 0 ? 'No followed people yet.' : 'No people match your search.';
|
|
divPeopleGrid.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
for (const pubkey of visible) {
|
|
const profile = getCachedProfile(pubkey);
|
|
const card = document.createElement('div');
|
|
card.className = 'personCard';
|
|
|
|
const avatar = createAvatarElement(pubkey, profile);
|
|
|
|
const main = document.createElement('div');
|
|
main.className = 'personMain';
|
|
main.addEventListener('click', () => {
|
|
window.location.href = `./post.html?npub=${encodeURIComponent(pubkey)}&profile=true&post=false`;
|
|
});
|
|
|
|
const name = document.createElement('div');
|
|
name.className = 'personName';
|
|
name.textContent = String(profile?.display_name || profile?.name || shortPubkey(pubkey));
|
|
|
|
const pk = document.createElement('div');
|
|
pk.className = 'personPubkey';
|
|
pk.textContent = pubkey;
|
|
|
|
main.appendChild(name);
|
|
main.appendChild(pk);
|
|
|
|
const btnRemove = document.createElement('button');
|
|
btnRemove.className = 'personRemove';
|
|
btnRemove.textContent = 'Remove';
|
|
btnRemove.addEventListener('click', async (ev) => {
|
|
ev.stopPropagation();
|
|
await removePerson(pubkey);
|
|
});
|
|
|
|
card.appendChild(avatar);
|
|
card.appendChild(main);
|
|
card.appendChild(btnRemove);
|
|
divPeopleGrid.appendChild(card);
|
|
}
|
|
}
|
|
|
|
let hydrateInFlight = null;
|
|
const hydratePendingPubkeys = new Set();
|
|
|
|
async function runHydrateProfiles(pubkeys) {
|
|
const ordered = getOrderedPubkeys().filter((pk) => pubkeys.includes(pk));
|
|
dlog('hydrateProfiles:start', {
|
|
requested: pubkeys.length,
|
|
ordered: ordered.length,
|
|
cachedAlready: ordered.filter((pk) => Boolean(getCachedProfile(pk))).length,
|
|
relayRequestedAlready: ordered.filter((pk) => profileRelayRequested.has(pk)).length
|
|
});
|
|
|
|
let cacheHitCount = 0;
|
|
let cacheMissCount = 0;
|
|
|
|
const cacheTasks = ordered.map(async (pk) => {
|
|
if (getCachedProfile(pk)) {
|
|
cacheHitCount++;
|
|
return;
|
|
}
|
|
try {
|
|
const profile = await profileCacheApi.fetchProfile(pk, { allowNetwork: false, backgroundRefresh: true, logPrefix: '[people.html]' });
|
|
if (profile) {
|
|
cacheHitCount++;
|
|
} else {
|
|
cacheMissCount++;
|
|
}
|
|
} catch (_e) {
|
|
cacheMissCount++;
|
|
// non-fatal
|
|
}
|
|
});
|
|
|
|
await Promise.all(cacheTasks);
|
|
dlog('hydrateProfiles:cache-phase-complete', { cacheHitCount, cacheMissCount, totalCachedNow: ordered.filter((pk) => Boolean(getCachedProfile(pk))).length });
|
|
await renderPeople();
|
|
|
|
const missing = ordered.filter((pk) => !getCachedProfile(pk) && !profileRelayRequested.has(pk));
|
|
dlog('hydrateProfiles:missing-after-cache', { missing: missing.length });
|
|
if (missing.length === 0) return;
|
|
|
|
setStatus(`Fetching ${missing.length} missing profiles from relays in batches...`);
|
|
|
|
for (let i = 0; i < missing.length; i += PROFILE_BATCH_SIZE) {
|
|
const batch = missing.slice(i, i + PROFILE_BATCH_SIZE);
|
|
batch.forEach((pk) => profileRelayRequested.add(pk));
|
|
|
|
const batchIndex = Math.floor(i / PROFILE_BATCH_SIZE) + 1;
|
|
const batchTotal = Math.ceil(missing.length / PROFILE_BATCH_SIZE);
|
|
dlog('hydrateProfiles:relay-batch', { batchIndex, batchTotal, size: batch.length, first: batch[0], last: batch[batch.length - 1] });
|
|
|
|
setStatus(`Fetching profile batch ${batchIndex}/${batchTotal}...`);
|
|
|
|
subscribe(
|
|
{ kinds: [0], authors: batch },
|
|
{ closeOnEose: true, cacheUsage: 'ONLY_RELAY' }
|
|
);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
}
|
|
|
|
setStatus(`Loaded ${followedPubkeys.length} followed people`);
|
|
dlog('hydrateProfiles:relay-phase-queued', { queued: missing.length });
|
|
|
|
const unresolved = ordered.filter((pk) => !getCachedProfile(pk));
|
|
if (unresolved.length > 0) {
|
|
dlog('hydrateProfiles:relay-fallback:needed', { unresolved: unresolved.length });
|
|
await fetchMissingProfilesFromAllRelays(unresolved);
|
|
}
|
|
}
|
|
|
|
async function hydrateProfiles(pubkeys) {
|
|
const requested = dedupePubkeys(pubkeys);
|
|
requested.forEach((pk) => hydratePendingPubkeys.add(pk));
|
|
|
|
if (hydrateInFlight) {
|
|
dlog('hydrateProfiles:guard-queued', {
|
|
requested: requested.length,
|
|
pending: hydratePendingPubkeys.size
|
|
});
|
|
return hydrateInFlight;
|
|
}
|
|
|
|
hydrateInFlight = (async () => {
|
|
try {
|
|
while (hydratePendingPubkeys.size > 0) {
|
|
const cyclePubkeys = Array.from(hydratePendingPubkeys);
|
|
hydratePendingPubkeys.clear();
|
|
dlog('hydrateProfiles:guard-cycle-start', { cycleCount: cyclePubkeys.length });
|
|
await runHydrateProfiles(cyclePubkeys);
|
|
}
|
|
} finally {
|
|
hydrateInFlight = null;
|
|
dlog('hydrateProfiles:guard-idle');
|
|
}
|
|
})();
|
|
|
|
return hydrateInFlight;
|
|
}
|
|
|
|
async function fetchMissingProfilesFromAllRelays(missingPubkeys) {
|
|
const rows = dedupePubkeys(missingPubkeys);
|
|
if (!rows.length) return;
|
|
|
|
dlog('hydrateProfiles:relay-fallback:start', { count: rows.length });
|
|
|
|
for (let i = 0; i < rows.length; i += PROFILE_BATCH_SIZE) {
|
|
const batch = rows.slice(i, i + PROFILE_BATCH_SIZE);
|
|
const batchIndex = Math.floor(i / PROFILE_BATCH_SIZE) + 1;
|
|
const batchTotal = Math.ceil(rows.length / PROFILE_BATCH_SIZE);
|
|
|
|
try {
|
|
const byRelay = await fetchEventsFromAllRelays({ kinds: [0], authors: batch });
|
|
const newestByPubkey = new Map();
|
|
|
|
for (const events of Object.values(byRelay || {})) {
|
|
if (!Array.isArray(events)) continue;
|
|
for (const evt of events) {
|
|
if (!evt || evt.kind !== 0 || typeof evt.pubkey !== 'string') continue;
|
|
const prev = newestByPubkey.get(evt.pubkey);
|
|
if (!prev || Number(evt.created_at || 0) > Number(prev.created_at || 0)) {
|
|
newestByPubkey.set(evt.pubkey, evt);
|
|
}
|
|
}
|
|
}
|
|
|
|
let applied = 0;
|
|
for (const evt of newestByPubkey.values()) {
|
|
try {
|
|
const parsed = JSON.parse(evt.content || '{}');
|
|
profileCacheApi.seedProfileCache(evt.pubkey, parsed);
|
|
applied++;
|
|
} catch (_e) {
|
|
// non-fatal
|
|
}
|
|
}
|
|
|
|
dlog('hydrateProfiles:relay-fallback:batch-done', {
|
|
batchIndex,
|
|
batchTotal,
|
|
requested: batch.length,
|
|
applied
|
|
});
|
|
|
|
await renderPeople();
|
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
} catch (error) {
|
|
dlog('hydrateProfiles:relay-fallback:batch-error', {
|
|
batchIndex,
|
|
batchTotal,
|
|
message: error?.message || String(error)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function followListsEqual(a, b) {
|
|
if (a.length !== b.length) return false;
|
|
const aSet = new Set(a);
|
|
for (const pk of b) {
|
|
if (!aSet.has(pk)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function applyKind3Event(evt, source = 'unknown') {
|
|
const next = dedupePubkeys(parseFollowedPubkeysFromKind3(evt));
|
|
const changed = !followListsEqual(followedPubkeys, next);
|
|
followedPubkeys = next;
|
|
for (const pk of [...profileRelayRequested]) {
|
|
if (!followedPubkeys.includes(pk)) profileRelayRequested.delete(pk);
|
|
}
|
|
dlog('applyKind3Event', {
|
|
source,
|
|
followCount: followedPubkeys.length,
|
|
changed,
|
|
preview: followedPubkeys.slice(0, 5)
|
|
});
|
|
|
|
if (typeof evt?.content === 'string' && evt.content.trim()) {
|
|
try {
|
|
latestContactContent = JSON.parse(evt.content);
|
|
} catch (_err) {
|
|
latestContactContent = {};
|
|
}
|
|
}
|
|
|
|
setStatus(`Loaded ${followedPubkeys.length} followed people (${source})`);
|
|
renderPeople();
|
|
if (changed || source === 'cache') {
|
|
hydrateProfiles(followedPubkeys);
|
|
}
|
|
}
|
|
|
|
async function publishContacts() {
|
|
const tags = followedPubkeys.map((pk) => ['p', pk]);
|
|
const event = {
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
kind: 3,
|
|
tags,
|
|
content: JSON.stringify(latestContactContent || {})
|
|
};
|
|
return await publishEvent(event);
|
|
}
|
|
|
|
async function addPersonFromInput() {
|
|
try {
|
|
const pubkey = normalizePubkeyInput(inpAddPerson.value);
|
|
if (pubkey === currentPubkey) throw new Error('Cannot follow yourself from this page');
|
|
if (followedPubkeys.includes(pubkey)) {
|
|
setStatus('Already followed');
|
|
return;
|
|
}
|
|
|
|
followedPubkeys = dedupePubkeys([...followedPubkeys, pubkey]);
|
|
await renderPeople();
|
|
setStatus(`Adding ${shortPubkey(pubkey)}...`);
|
|
|
|
const result = await publishContacts();
|
|
setStatus(`Added ${shortPubkey(pubkey)} | relays ok: ${result.relayResults.successful.length}`);
|
|
|
|
inpAddPerson.value = '';
|
|
hydrateProfiles([pubkey]);
|
|
} catch (error) {
|
|
setStatus(`Add failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function removePerson(pubkey) {
|
|
const ok = window.confirm(`Remove ${shortPubkey(pubkey)} from follows?`);
|
|
if (!ok) return;
|
|
|
|
const next = followedPubkeys.filter((pk) => pk !== pubkey);
|
|
followedPubkeys = next;
|
|
await renderPeople();
|
|
setStatus(`Removing ${shortPubkey(pubkey)}...`);
|
|
|
|
try {
|
|
const result = await publishContacts();
|
|
setStatus(`Removed ${shortPubkey(pubkey)} | relays ok: ${result.relayResults.successful.length}`);
|
|
} catch (error) {
|
|
setStatus(`Remove publish failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function wireEvents() {
|
|
inpSearchPeople.addEventListener('input', () => renderPeople());
|
|
btnAddPerson.addEventListener('click', addPersonFromInput);
|
|
inpAddPerson.addEventListener('keydown', (ev) => {
|
|
if (ev.key === 'Enter') addPersonFromInput();
|
|
});
|
|
|
|
window.addEventListener('ndkEvent', (event) => {
|
|
const evt = event.detail;
|
|
if (!evt || !evt.kind) return;
|
|
|
|
if (evt.kind === 3 && evt.pubkey === currentPubkey) {
|
|
dlog('ndkEvent:kind3', { created_at: evt.created_at, tags: Array.isArray(evt.tags) ? evt.tags.length : 0 });
|
|
applyKind3Event(evt, 'subscription');
|
|
}
|
|
|
|
if (evt.kind === 0 && followedPubkeys.includes(evt.pubkey)) {
|
|
try {
|
|
const parsed = JSON.parse(evt.content || '{}');
|
|
profileCacheApi.seedProfileCache(evt.pubkey, parsed);
|
|
dlog('ndkEvent:kind0-profile', {
|
|
pubkey: evt.pubkey,
|
|
hasName: Boolean(parsed?.display_name || parsed?.name),
|
|
hasPicture: Boolean(parsed?.picture)
|
|
});
|
|
renderPeople();
|
|
} catch (_e) {
|
|
dlog('ndkEvent:kind0-profile-parse-failed', { pubkey: evt.pubkey });
|
|
// non-fatal
|
|
}
|
|
}
|
|
});
|
|
|
|
window.addEventListener('ndkRelayActivity', (event) => {
|
|
const { relayUrl, activity } = event.detail;
|
|
setRelayActivityState(relayUrl, activity);
|
|
});
|
|
}
|
|
|
|
async function loadInitialContacts() {
|
|
setStatus('Loading follows from cache...');
|
|
|
|
try {
|
|
const cached = await queryCache({ kinds: [3], authors: [currentPubkey], limit: 1 });
|
|
dlog('loadInitialContacts:cache-result', { count: cached?.length || 0 });
|
|
if (cached?.length) {
|
|
const latest = [...cached].sort((a, b) => Number(b.created_at || 0) - Number(a.created_at || 0))[0];
|
|
dlog('loadInitialContacts:using-cached-kind3', { created_at: latest?.created_at, tagCount: Array.isArray(latest?.tags) ? latest.tags.length : 0 });
|
|
applyKind3Event(latest, 'cache');
|
|
} else {
|
|
setStatus('No cached follows yet; waiting for relay data...');
|
|
}
|
|
} catch (error) {
|
|
console.warn('[people.html] cache load failed:', error.message);
|
|
}
|
|
|
|
dlog('loadInitialContacts:subscribe-kind3-cache-first');
|
|
subscribe({ kinds: [3], authors: [currentPubkey], limit: 1 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
|
|
|
|
dlog('loadInitialContacts:fetch-kind3-all-relays:start');
|
|
fetchEventsFromAllRelays({ kinds: [3], authors: [currentPubkey], limit: 1 })
|
|
.then((byRelay) => {
|
|
const relayCount = Object.keys(byRelay || {}).length;
|
|
const totalEvents = Object.values(byRelay || {}).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
|
dlog('loadInitialContacts:fetch-kind3-all-relays:done', { relayCount, totalEvents });
|
|
})
|
|
.catch((err) => {
|
|
dlog('loadInitialContacts:fetch-kind3-all-relays:error', { message: err?.message || String(err) });
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
initHamburgerMenu();
|
|
document.getElementById('divSvgHam')?.addEventListener('click', toggleNav);
|
|
|
|
const themeToggleButton = document.getElementById('themeToggleButton');
|
|
const logoutButton = document.getElementById('logoutButton');
|
|
|
|
themeToggleButton?.addEventListener('click', () => {
|
|
isDarkMode = !isDarkMode;
|
|
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
|
|
localStorage.setItem('sidenavWasOpen', isNavOpen ? 'true' : 'false');
|
|
window.location.reload();
|
|
});
|
|
|
|
logoutButton?.addEventListener('click', async () => {
|
|
try { await Logout(); } catch (error) { console.error(error); }
|
|
});
|
|
|
|
await initNDKPage();
|
|
currentPubkey = await getPubkey();
|
|
await injectHeaderAvatar(currentPubkey);
|
|
|
|
initFooterRelayStatus();
|
|
initSidenavRelaySection();
|
|
await initBlossomSection();
|
|
await UpdateFooter();
|
|
|
|
wireEvents();
|
|
await loadInitialContacts();
|
|
|
|
const sidenavWasOpen = localStorage.getItem('sidenavWasOpen');
|
|
if (sidenavWasOpen === 'true') {
|
|
localStorage.removeItem('sidenavWasOpen');
|
|
openNav();
|
|
}
|
|
|
|
updateIntervalId = setInterval(UpdateFooter, 1000);
|
|
await updateVersionDisplay();
|
|
setStatus(`Ready | ${followedPubkeys.length} followed`);
|
|
} catch (error) {
|
|
console.error('[people.html] Initialization failed:', error);
|
|
divBody.innerHTML = `<div style="text-align:center;padding:40px;color:var(--accent-color);">${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
main();
|
|
|