From 447c112d21198dcc26c74368576b416e2556e906 Mon Sep 17 00:00:00 2001
From: Jonathan Staab
Date: Sat, 17 Dec 2022 12:30:18 -0800
Subject: [PATCH] Working on scrolling stuff
---
README.md | 2 ++
src/relay/db.js | 8 ++++---
src/relay/index.js | 45 ++++++++++++++++++++++++++++--------
src/relay/pool.js | 31 +++++++++++--------------
src/routes/UserDetail.svelte | 5 +++-
src/util/misc.js | 35 +++++++++++++++-------------
src/views/Note.svelte | 15 ++++++------
src/views/NoteDetail.svelte | 2 +-
src/views/Notes.svelte | 36 +++++++++++------------------
9 files changed, 101 insertions(+), 78 deletions(-)
diff --git a/README.md b/README.md
index a28e7c47..72abd863 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
- [ ] Optimistically load events the user publishes (e.g. to reduce reflow for reactions/replies).
- Essentially, we can pretend to be our own in-memory relay.
- This allows us to keep a copy of all user data, and possibly user likes/reply parents
+- [ ] Support invoices https://twitter.com/jb55/status/1604131336247476224
# Bugs
@@ -44,6 +45,7 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
- [ ] Make user a livequery instead of a store
- [ ] Figure out if multiple relays congest response times because we wait for all eose
- [ ] Set default relay when storage is empty
+- [ ] Are connections closed when a relay is removed?
- https://vitejs.dev/guide/features.html#web-workers
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
- https://web.dev/module-workers/
diff --git a/src/relay/db.js b/src/relay/db.js
index 9398346d..75a810c6 100644
--- a/src/relay/db.js
+++ b/src/relay/db.js
@@ -1,6 +1,7 @@
import Dexie from 'dexie'
import {groupBy, prop, flatten, pick} from 'ramda'
import {ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
+import {now} from 'src/util/misc'
import {filterTags, findReply, findRoot} from 'src/util/nostr'
export const db = new Dexie('coracle/relay')
@@ -59,11 +60,12 @@ db.events.process = async events => {
for (const event of profileUpdates) {
const {pubkey, kind, content, tags} = event
const user = await db.users.where('pubkey').equals(pubkey).first()
+ const putUser = data => db.users.put({...user, ...data, pubkey, updated_at: now()})
await switcherFn(kind, {
- 0: () => db.users.put({...user, ...JSON.parse(content), pubkey}),
- 3: () => db.users.put({...user, petnames: tags, pubkey}),
- 12165: () => db.users.put({...user, muffle: tags, pubkey}),
+ 0: () => putUser(JSON.parse(content)),
+ 3: () => putUser({petnames: tags}),
+ 12165: () => putUser({muffle: tags}),
default: () => {
console.log(`Received unsupported event type ${event.kind}`)
},
diff --git a/src/relay/index.js b/src/relay/index.js
index bf4b01be..29e8d0f4 100644
--- a/src/relay/index.js
+++ b/src/relay/index.js
@@ -1,5 +1,5 @@
import {liveQuery} from 'dexie'
-import {pluck, isNil} from 'ramda'
+import {pluck, uniq, objOf, isNil} from 'ramda'
import {ensurePlural, createMap, ellipsize, first} from 'hurdak/lib/hurdak'
import {now, timedelta} from 'src/util/misc'
import {escapeHtml} from 'src/util/html'
@@ -16,16 +16,26 @@ const lq = f => liveQuery(async () => {
}
})
-const ensureContext = async e => {
- const user = await db.users.where('pubkey').equals(e.pubkey).first()
+const ensurePerson = async ({pubkey}) => {
+ const user = await db.users.where('pubkey').equals(pubkey).first()
// Throttle updates for users
if (!user || user.updated_at < now() - timedelta(1, 'hours')) {
- await pool.syncUserInfo({pubkey: e.pubkey, ...user})
+ await pool.syncUserInfo({pubkey, ...user})
}
+}
- // TODO optimize this like user above so we're not double-fetching
- await pool.fetchContext(e)
+const ensureContext = async events => {
+ const ids = events.flatMap(e => filterTags({tag: "e"}, e).concat(e.id))
+ const people = uniq(pluck('pubkey', events)).map(objOf('pubkey'))
+
+ await Promise.all([
+ people.map(ensurePerson),
+ pool.fetchEvents([
+ {kinds: [1, 5, 7], '#e': ids},
+ {kinds: [1, 5], ids},
+ ]),
+ ])
}
const prefilterEvents = filter => {
@@ -50,6 +60,8 @@ const filterEvents = filter => {
if (filter.ids && !filter.ids.includes(e.id)) return false
if (filter.authors && !filter.authors.includes(e.pubkey)) return false
if (filter.kinds && !filter.kinds.includes(e.kind)) return false
+ if (filter.since && filter.since > e.created_at) return false
+ if (filter.until && filter.until < e.created_at) return false
if (!isNil(filter.content) && filter.content !== e.content) return false
return true
@@ -78,12 +90,27 @@ const findReaction = async (id, filter) =>
const countReactions = async (id, filter) =>
(await filterReactions(id, filter)).length
-const findNote = async id => {
+const findNote = async (id, giveUp = false) => {
const [note, children] = await Promise.all([
db.events.get(id),
db.events.where('reply').equals(id),
])
+ // If we don't have it, try to retrieve it
+ if (!note) {
+ console.warning(`Failed to find context for note ${id}`)
+
+ if (giveUp) {
+ return null
+ }
+
+ await ensureContext([
+ await pool.fetchEvents({ids: [id]}),
+ ])
+
+ return findNote(id, true)
+ }
+
const [replies, reactions, user, html] = await Promise.all([
children.clone().filter(e => e.kind === 1).toArray(),
children.clone().filter(e => e.kind === 7).toArray(),
@@ -130,6 +157,6 @@ const filterAlerts = async (user, filter) => {
}
export default {
- db, pool, lq, ensureContext, filterEvents, filterReactions, countReactions,
- findReaction, filterReplies, findNote, renderNote, filterAlerts,
+ db, pool, lq, ensurePerson, ensureContext, filterEvents, filterReactions,
+ countReactions, findReaction, filterReplies, findNote, renderNote, filterAlerts,
}
diff --git a/src/relay/pool.js b/src/relay/pool.js
index 21d3f9c9..762d0d8c 100644
--- a/src/relay/pool.js
+++ b/src/relay/pool.js
@@ -1,8 +1,8 @@
import {uniqBy, prop} from 'ramda'
import {relayPool, getPublicKey} from 'nostr-tools'
-import {noop} from 'hurdak/lib/hurdak'
+import {noop, range} from 'hurdak/lib/hurdak'
import {now, randomChoice, timedelta, getLocalJson, setLocalJson} from "src/util/misc"
-import {filterTags, getTagValues} from "src/util/nostr"
+import {getTagValues} from "src/util/nostr"
import {db} from 'src/relay/db'
// ============================================================================
@@ -73,11 +73,7 @@ class Channel {
}
}
-export const channels = [
- new Channel('a'),
- new Channel('b'),
- new Channel('c'),
-]
+export const channels = range(0, 10).map(i => new Channel(i.toString()))
const req = filter => randomChoice(channels).all(filter)
@@ -117,7 +113,9 @@ const publishEvent = event => {
const loadEvents = async filter => {
const events = await req(filter)
- db.events.process(events)
+ await db.events.process(events)
+
+ return events
}
const syncUserInfo = async user => {
@@ -137,16 +135,12 @@ const syncUserInfo = async user => {
return person
}
-const fetchContext = async event => {
- const events = await req([
- {kinds: [5, 7], '#e': [event.id]},
- {kinds: [5], 'ids': filterTags({tag: "e"}, event)},
- ])
-
- db.events.process(events)
+const fetchEvents = async filter => {
+ db.events.process(await req(filter))
}
let syncSub = null
+let syncChan = new Channel('sync')
const sync = async user => {
if (syncSub) {
@@ -155,7 +149,10 @@ const sync = async user => {
if (!user) return
+ // Get user info right away
const {petnames, pubkey} = await syncUserInfo(user)
+
+ // Don't grab nothing, but don't grab everything either
const since = Math.max(
now() - timedelta(3, 'days'),
Math.min(
@@ -167,7 +164,7 @@ const sync = async user => {
setLocalJson('pool/lastSync', now())
// Populate recent activity in network so the user has something to look at right away
- syncSub = randomChoice(channels).sub(
+ syncSub = syncChan.sub(
[{since, authors: getTagValues(petnames).concat(pubkey)},
{since, '#p': [pubkey]}],
db.events.process
@@ -176,5 +173,5 @@ const sync = async user => {
export default {
getPubkey, addRelay, removeRelay, setPrivateKey, setPublicKey,
- publishEvent, loadEvents, syncUserInfo, fetchContext, sync,
+ publishEvent, loadEvents, syncUserInfo, fetchEvents, sync,
}
diff --git a/src/routes/UserDetail.svelte b/src/routes/UserDetail.svelte
index 35037f85..12055b74 100644
--- a/src/routes/UserDetail.svelte
+++ b/src/routes/UserDetail.svelte
@@ -2,6 +2,7 @@
import {find} from 'ramda'
import {fly} from 'svelte/transition'
import {navigate} from 'svelte-routing'
+ import {timedelta} from 'src/util/misc'
import Tabs from "src/partials/Tabs.svelte"
import Button from "src/partials/Button.svelte"
import Notes from "src/views/Notes.svelte"
@@ -13,6 +14,8 @@
export let pubkey
export let activeTab
+ relay.ensurePerson({pubkey})
+
const user = relay.lq(() => relay.db.users.get(pubkey))
let following = $currentUser && find(t => t[1] === pubkey, $currentUser.petnames)
@@ -78,7 +81,7 @@
{#if activeTab === 'notes'}
-
+
{:else if activeTab === 'likes'}
{:else if activeTab === 'network'}
diff --git a/src/util/misc.js b/src/util/misc.js
index 6cbaf290..d0da13ac 100644
--- a/src/util/misc.js
+++ b/src/util/misc.js
@@ -1,5 +1,4 @@
import {pluck} from "ramda"
-import {debounce} from 'throttle-debounce'
import Fuse from "fuse.js/dist/fuse.min.js"
export const fuzzy = (data, opts = {}) => {
@@ -52,27 +51,31 @@ export const formatTimestamp = ts => {
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
export const createScroller = loadMore => {
- const onScroll = debounce(1000, async () => {
- /* eslint no-constant-condition: 0 */
+ /* eslint no-constant-condition: 0 */
- while (true) {
- // While we have empty space, fill it
- const {scrollY, innerHeight} = window
- const {scrollHeight} = document.body
+ let done = false
- if (scrollY + innerHeight + 600 < scrollHeight) {
- break
- }
+ const check = async () => {
+ // While we have empty space, fill it
+ const {scrollY, innerHeight} = window
+ const {scrollHeight} = document.body
- loadMore()
-
- await sleep(1000)
+ if (scrollY + innerHeight + 600 > scrollHeight) {
+ await loadMore()
}
- })
- onScroll()
+ await sleep(300)
- return onScroll
+ if (!done) {
+ requestAnimationFrame(check)
+ }
+ }
+
+ requestAnimationFrame(check)
+
+ return () => {
+ done = true
+ }
}
export const randomChoice = xs => xs[Math.floor(Math.random() * xs.length)]
diff --git a/src/views/Note.svelte b/src/views/Note.svelte
index e094c93a..396a4aeb 100644
--- a/src/views/Note.svelte
+++ b/src/views/Note.svelte
@@ -7,7 +7,6 @@
import {findReply} from "src/util/nostr"
import Preview from 'src/partials/Preview.svelte'
import Anchor from 'src/partials/Anchor.svelte'
- import relay from 'src/relay'
import {dispatch} from "src/state/dispatch"
import {settings, user, modal} from "src/state/app"
import {formatTimestamp} from 'src/util/misc'
@@ -34,8 +33,6 @@
flag = find(whereEq({pubkey: $user?.pubkey}), flags)
}
- relay.ensureContext(note)
-
const onClick = e => {
if (!['I'].includes(e.target.tagName) && !hasParent('a', e.target)) {
modal.set({note})
@@ -112,14 +109,16 @@
deleteReaction(flag)}>Unflag
{:else}
-
- {@html note.html}
+
+
{@html note.html}
{#if link}
-
e.stopPropagation()}>
-
+
+
e.stopPropagation()}>
+
+
{/if}
-
+
relay.findNote(note, {showEntire: true}))
+ const observable = relay.lq(() => relay.findNote(note.id, {showEntire: true}))
{#if $observable}
diff --git a/src/views/Notes.svelte b/src/views/Notes.svelte
index 8f7e0891..0eadc226 100644
--- a/src/views/Notes.svelte
+++ b/src/views/Notes.svelte
@@ -1,35 +1,35 @@
-
-
{#each ($notes || []) as n (n.id)}
-
+
{/each}
-{#if $notes?.length === 0}
-
-{:else}
-{/if}