mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Fix alerts
This commit is contained in:
parent
7ed121f560
commit
a7a41a659c
29
README.md
29
README.md
@ -19,11 +19,13 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||
- [ ] An actual readme
|
||||
- [ ] Server discovery and relay publishing - https://github.com/nostr-protocol/nips/pull/32/files
|
||||
- [ ] Support invoices https://twitter.com/jb55/status/1604131336247476224
|
||||
- [ ] Indexing server
|
||||
- [ ] Add a coracle relay
|
||||
- [ ] Expand/collapse large threads
|
||||
- [ ] Don't send note on enter, allow linebreaks
|
||||
- [ ] NIP 05
|
||||
|
||||
# Bugs
|
||||
|
||||
- [ ] Use https://nostr.watch/relays.json to populate relays
|
||||
- [ ] Completely redo notes fetching, it's buggy as heck
|
||||
- [ ] uniq and sortBy are sprinkled all over the place, figure out a better solution
|
||||
- [ ] Search page is slow and likes don't show up. Probably move this server-side
|
||||
@ -35,16 +37,22 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||
- [ ] We're sending client=astral tags, event id 125ff9dc495f65d302e8d95ea6f9385106cc31b81c80e8c582b44be92fa50c44
|
||||
- [ ] Add notification for slow relays
|
||||
- [ ] Wait for 60% or so of relays to eose to balance completeness with speed
|
||||
- [ ] Add a CSP
|
||||
- [ ] Add a CSP, check for XSS in image urls
|
||||
|
||||
# Current update
|
||||
|
||||
- [ ] Add depth to findNote
|
||||
- [ ] Add date to alerts
|
||||
- [ ] Re-implement muffle
|
||||
- Don't store muffled events, when muffle changes delete them
|
||||
- [ ] Delete old events
|
||||
- [ ] Sync account updates to user for e.g. muffle settings
|
||||
- [ ] Make sure login/out, no user usage works
|
||||
- [ ] Add a re-sync/clear cache button
|
||||
- [ ] Note detail context not showing when navigating between note details (e.g. to parent)
|
||||
- [ ] Show reply to on feed
|
||||
- [ ] Write blog post
|
||||
- [ ] Get rid of dispatch
|
||||
- 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/
|
||||
@ -52,20 +60,17 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||
- [x] Sync user
|
||||
- [x] Based on petnames, sync network to 2 or 3 degrees of separation
|
||||
- When a user is added/removed, sync them and add to or remove from network
|
||||
- [ ] Add cursor object to handle since/until/last sync
|
||||
- [ ] Separate fetching and loading from the db
|
||||
- [x] Add cursor object to handle since/until/last sync
|
||||
- [x] Separate fetching and loading from the db
|
||||
- Each route should have a fetcher and loader.
|
||||
- The fetcher should keep track of the range of notes it has already gotten
|
||||
- Separate helper functions into loaders and fetchers
|
||||
- [ ] Main fetch requests:
|
||||
- [x] Main fetch requests:
|
||||
- Fetch feed by name, since last sync
|
||||
- Fetch person, including feed
|
||||
- Fetch note, including context
|
||||
- This is based on detail pages. Each request should check local db and fall back to network, all within an await.
|
||||
|
||||
# Problems to solve
|
||||
|
||||
- [ ] How will newcomers get followed?
|
||||
|
||||
# Quick fixes
|
||||
|
||||
- Add default relay and relay list
|
||||
- Show relay nav item when no user
|
||||
- check security = user.name, image, use https://github.com/cure53/DOMPurify
|
||||
|
@ -10,7 +10,7 @@
|
||||
import {Router, Route, links, navigate} from "svelte-routing"
|
||||
import {globalHistory} from "svelte-routing/src/history"
|
||||
import {hasParent} from 'src/util/html'
|
||||
import {timedelta} from 'src/util/misc'
|
||||
import {timedelta, now} from 'src/util/misc'
|
||||
import {store as toast} from "src/state/toast"
|
||||
import {modal, alerts, settings} from "src/state/app"
|
||||
import relay, {user, connections} from 'src/relay'
|
||||
@ -30,6 +30,8 @@
|
||||
import Person from "src/routes/Person.svelte"
|
||||
import NoteCreate from "src/routes/NoteCreate.svelte"
|
||||
|
||||
window.relay = relay
|
||||
|
||||
export let url = ""
|
||||
|
||||
const menuIsOpen = writable(false)
|
||||
@ -42,9 +44,7 @@
|
||||
let scrollY
|
||||
let suspendedSubs = []
|
||||
let mostRecentAlert = relay.lq(async () => {
|
||||
const [e] = await relay
|
||||
.filterAlerts($user, {since: $alerts.since})
|
||||
.limit(1).reverse().sortBy('created_at')
|
||||
const [e] = await relay.filterAlerts($user, 1)
|
||||
|
||||
return e?.created_at
|
||||
})
|
||||
@ -70,10 +70,6 @@
|
||||
}, 200)
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
relay.pool.syncNetwork()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Close menu on click outside
|
||||
document.querySelector("html").addEventListener("click", e => {
|
||||
@ -82,7 +78,18 @@
|
||||
}
|
||||
})
|
||||
|
||||
return modal.subscribe($modal => {
|
||||
let prevPubkey = null
|
||||
|
||||
const unsubUser = user.subscribe($user => {
|
||||
if ($user && $user.pubkey !== prevPubkey) {
|
||||
relay.pool.syncNetwork()
|
||||
relay.pool.listenForEvents('App/alerts', {'#p': [$user.pubkey], since: now()})
|
||||
}
|
||||
|
||||
prevPubkey = $user?.pubkey
|
||||
})
|
||||
|
||||
const unsubModal = modal.subscribe($modal => {
|
||||
// Keep scroll position on body, but don't allow scrolling
|
||||
if ($modal) {
|
||||
// This is not idempotent, so don't duplicate it
|
||||
@ -97,6 +104,11 @@
|
||||
window.scrollTo(0, scrollY)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubUser()
|
||||
unsubModal()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
<script>
|
||||
import {Link} from 'svelte-routing'
|
||||
|
||||
export let person
|
||||
</script>
|
||||
|
||||
<a href={`/people/${person.pubkey}/notes`} class="flex gap-2 items-center relative z-10">
|
||||
<Link
|
||||
to={`/people/${person.pubkey}/notes`}
|
||||
class="flex gap-2 items-center relative z-10"
|
||||
on:click={e => e.stopPropagation()}>
|
||||
<div
|
||||
class="overflow-hidden w-4 h-4 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({person.picture})" />
|
||||
<span class="text-lg font-bold">{person.name || person.pubkey.slice(0, 8)}</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
49
src/partials/Like.svelte
Normal file
49
src/partials/Like.svelte
Normal file
@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import {fly} from 'svelte/transition'
|
||||
import {ellipsize, quantify} from 'hurdak/src/core'
|
||||
import Badge from "src/partials/Badge.svelte"
|
||||
import {formatTimestamp} from 'src/util/misc'
|
||||
import {modal} from 'src/state/app'
|
||||
|
||||
export let note
|
||||
|
||||
let isOpen = false
|
||||
|
||||
const openPopover = e => {
|
||||
e.stopPropagation()
|
||||
|
||||
isOpen = true
|
||||
}
|
||||
|
||||
const closePopover = e => {
|
||||
e.stopPropagation()
|
||||
|
||||
isOpen = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="py-2 px-3 flex flex-col gap-2 text-white cursor-pointer transition-all
|
||||
border border-solid border-black hover:border-medium hover:bg-dark"
|
||||
on:click={() => modal.set({note})}>
|
||||
<div class="flex gap-2 items-center justify-between relative">
|
||||
<span class="cursor-pointer" on:click={openPopover}>
|
||||
{quantify(note.people.length, 'person', 'people')} liked your note.
|
||||
</span>
|
||||
{#if isOpen}
|
||||
<div transition:fly={{y: 20}} class="fixed inset-0 z-10" on:click={closePopover} />
|
||||
<div
|
||||
transition:fly={{y: 20}}
|
||||
class="absolute bottom-0 -ml-3 mb-12 py-2 px-4 rounded border border-solid border-medium
|
||||
bg-dark grid grid-cols-3 gap-y-2 gap-x-4 z-10">
|
||||
{#each note.people as person (person.pubkey)}
|
||||
<Badge {person} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
|
||||
</div>
|
||||
<div class="ml-6 text-light">
|
||||
{ellipsize(note.content, 120)}
|
||||
</div>
|
||||
</div>
|
@ -2,7 +2,7 @@
|
||||
import {onDestroy} from 'svelte'
|
||||
import {createScroller} from 'src/util/misc'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import Note from "src/views/Note.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import relay from 'src/relay'
|
||||
|
||||
export let loadNotes
|
||||
@ -11,11 +11,15 @@
|
||||
let notes
|
||||
let limit = 0
|
||||
|
||||
onDestroy(createScroller(async () => {
|
||||
const scroller = createScroller(async () => {
|
||||
limit += 20
|
||||
|
||||
notes = relay.lq(() => loadNotes(limit))
|
||||
}))
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
scroller.stop()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if notes}
|
@ -1,7 +1,7 @@
|
||||
import {liveQuery} from 'dexie'
|
||||
import {get} from 'svelte/store'
|
||||
import {pluck, uniqBy, groupBy, concat, without, prop, uniq, objOf, isNil, identity} from 'ramda'
|
||||
import {ensurePlural, createMap, ellipsize, first} from 'hurdak/lib/hurdak'
|
||||
import {pluck, take, uniqBy, groupBy, concat, without, prop, isNil, identity} from 'ramda'
|
||||
import {ensurePlural, createMap, ellipsize} from 'hurdak/lib/hurdak'
|
||||
import {escapeHtml} from 'src/util/html'
|
||||
import {filterTags, findReply, findRoot} from 'src/util/nostr'
|
||||
import {db} from 'src/relay/db'
|
||||
@ -17,21 +17,6 @@ const lq = f => liveQuery(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Filter builders
|
||||
|
||||
export const buildNoteContextFilter = async (note, extra = {}) => {
|
||||
const replyId = findReply(note)
|
||||
const filter = [
|
||||
{...extra, kinds: [1, 5, 7], '#e': [note.id]},
|
||||
{kinds: [0], authors: [note.pubkey]}]
|
||||
|
||||
if (replyId && !await db.events.get(replyId)) {
|
||||
filter.push({...extra, kinds: [1], ids: [replyId]})
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// Utils for querying dexie - these return collections, not arrays
|
||||
|
||||
const prefilterEvents = filter => {
|
||||
@ -64,22 +49,20 @@ const filterEvents = filter => {
|
||||
|
||||
return true
|
||||
})
|
||||
.reverse()
|
||||
.sortBy('created_at')
|
||||
}
|
||||
|
||||
const filterReplies = async (id, filter) => {
|
||||
const tags = db.tags.where('value').equals(id).filter(t => t.mark === 'reply')
|
||||
const ids = pluck('event', await tags.toArray())
|
||||
const replies = await filterEvents({...filter, kinds: [1], ids}).toArray()
|
||||
const events = await db.events.where('reply').equals(id).toArray()
|
||||
|
||||
return replies
|
||||
return events.filter(e => e.kind === 1)
|
||||
}
|
||||
|
||||
const filterReactions = async (id, filter) => {
|
||||
const tags = db.tags.where('value').equals(id).filter(t => t.mark === 'reply')
|
||||
const ids = pluck('event', await tags.toArray())
|
||||
const reactions = await filterEvents({...filter, kinds: [7], ids}).toArray()
|
||||
const events = await db.events.where('reply').equals(id).toArray()
|
||||
|
||||
return reactions
|
||||
return events.filter(e => e.kind === 7)
|
||||
}
|
||||
|
||||
const findNote = async (id, {showEntire = false} = {}) => {
|
||||
@ -117,7 +100,7 @@ const findNote = async (id, {showEntire = false} = {}) => {
|
||||
|
||||
const annotateChunk = async chunk => {
|
||||
const ancestorIds = concat(chunk.map(findRoot), chunk.map(findReply)).filter(identity)
|
||||
const ancestors = await filterEvents({kinds: [1], ids: ancestorIds}).toArray()
|
||||
const ancestors = await filterEvents({kinds: [1], ids: ancestorIds})
|
||||
|
||||
const allNotes = uniqBy(prop('id'), chunk.concat(ancestors))
|
||||
const notesById = createMap('id', allNotes)
|
||||
@ -171,12 +154,24 @@ const renderNote = async (note, {showEntire = false}) => {
|
||||
})
|
||||
}
|
||||
|
||||
const filterAlerts = async (person, filter) => {
|
||||
const filterAlerts = async (person, limit) => {
|
||||
const tags = db.tags.where('value').equals(person.pubkey)
|
||||
const ids = pluck('event', await tags.toArray())
|
||||
const events = await filterEvents({...filter, kinds: [1, 7], ids})
|
||||
const alerts = take(limit + 1, await filterEvents({kinds: [1, 7], ids}))
|
||||
|
||||
return events
|
||||
return alerts.filter(e => {
|
||||
// Don't show people's own stuff
|
||||
if (e.pubkey === person.pubkey) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only notify users about positive reactions
|
||||
if (e.kind === 7 && !['', '+'].includes(e.content)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Synchronization
|
||||
@ -210,11 +205,32 @@ const unfollow = async pubkey => {
|
||||
// Methods that wil attempt to load from the database and fall back to the network.
|
||||
// This is intended only for bootstrapping listeners
|
||||
|
||||
const getOrLoadNote = async (id, {showEntire = false} = {}) => {
|
||||
const loadNoteContext = async (note, {loadParent = false} = {}) => {
|
||||
// Load note context - this assumes that we are looking at a feed, and so
|
||||
// we already have the note's parent and its likes loaded.
|
||||
const filter = [{kinds: [1, 5, 7], '#e': [note.id]}]
|
||||
|
||||
if (!prop(note.pubkey, get(db.people))) {
|
||||
filter.push({kinds: [0], authors: [note.pubkey]})
|
||||
}
|
||||
|
||||
await pool.loadEvents(filter)
|
||||
const replyId = findReply(note)
|
||||
|
||||
if (loadParent && replyId) {
|
||||
await getOrLoadNote(replyId)
|
||||
}
|
||||
}
|
||||
|
||||
const getOrLoadNote = async id => {
|
||||
if (!await db.events.get(id)) {
|
||||
await pool.loadEvents({kinds: [1], ids: [id]})
|
||||
}
|
||||
|
||||
const note = await db.events.get(id)
|
||||
|
||||
if (!note) {
|
||||
return first(await pool.loadEvents({kinds: [1], ids: [id]}))
|
||||
if (note) {
|
||||
await loadNoteContext(note, {loadParent: true})
|
||||
}
|
||||
|
||||
return note
|
||||
@ -254,7 +270,7 @@ export const network = db.network
|
||||
export const connections = db.connections
|
||||
|
||||
export default {
|
||||
db, pool, lq, buildNoteContextFilter, filterEvents, getOrLoadNote,
|
||||
filterReplies, findNote, annotateChunk, renderNote, filterAlerts,
|
||||
login, addRelay, removeRelay, follow, unfollow,
|
||||
db, pool, lq, filterEvents, getOrLoadNote, filterReplies, findNote,
|
||||
annotateChunk, renderNote, filterAlerts, login, addRelay, removeRelay,
|
||||
follow, unfollow, loadNoteContext,
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ class Channel {
|
||||
const sub = pool.sub({filter, cb: onEvent}, this.name, onEose)
|
||||
|
||||
const done = () => {
|
||||
sub.unsub()
|
||||
if (this.status === 'busy') {
|
||||
sub.unsub()
|
||||
}
|
||||
|
||||
this.release()
|
||||
}
|
||||
@ -183,6 +185,11 @@ const syncNetwork = async () => {
|
||||
if (pubkeys.length === 0) {
|
||||
pubkeys = [
|
||||
"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", // hodlbod
|
||||
"472f440f29ef996e92a186b8d320ff180c855903882e59d50de1b8bd5669301e", // Marty Bent
|
||||
"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", // Jack
|
||||
"85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204", // Preston
|
||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55
|
||||
"c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0", // Jack Mallers
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -1,55 +1,119 @@
|
||||
<script>
|
||||
import {propEq, uniqBy, prop, sortBy} from 'ramda'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {now} from 'src/util/misc'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import {ellipsize} from 'hurdak/src/core'
|
||||
import relay, {user} from 'src/relay'
|
||||
import {alerts, modal} from 'src/state/app'
|
||||
import Badge from "src/partials/Badge.svelte"
|
||||
import Note from 'src/views/Note.svelte'
|
||||
import relay, {people, user} from 'src/relay'
|
||||
import {alerts} from 'src/state/app'
|
||||
import {now, timedelta, createScroller, Cursor, getLastSync} from 'src/util/misc'
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from 'src/partials/Note.svelte'
|
||||
import Like from 'src/partials/Like.svelte'
|
||||
|
||||
const events = relay.lq(async () => {
|
||||
const alerts = await relay.filterAlerts($user)
|
||||
const events = await alerts.limit(10).reverse().sortBy('created_at')
|
||||
let sub
|
||||
let scroller
|
||||
let notes
|
||||
let limit = 0
|
||||
|
||||
return events
|
||||
// Add parent in
|
||||
.map(e => ({...e, parent: relay.filterEvents({ids: [findReply(e)]}).first()}))
|
||||
// Only show stuff if it's a direct reply to my note
|
||||
.filter(e => e.parent?.pubkey === $user.pubkey)
|
||||
const cursor = new Cursor(
|
||||
getLastSync('routes/Alerts'),
|
||||
timedelta(1, 'days')
|
||||
)
|
||||
|
||||
onMount(async () => {
|
||||
sub = await relay.pool.listenForEvents(
|
||||
'routes/Alerts',
|
||||
[{kinds: [1, 7], '#p': [$user.pubkey], since: cursor.since}],
|
||||
onEvent
|
||||
)
|
||||
|
||||
scroller = createScroller(async () => {
|
||||
limit += 20
|
||||
|
||||
notes = relay.lq(() => loadNotes(limit))
|
||||
})
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
sub?.unsub()
|
||||
scroller?.stop()
|
||||
})
|
||||
|
||||
const onEvent = e => {
|
||||
if (e.kind === 1) {
|
||||
relay.loadNoteContext(e)
|
||||
}
|
||||
|
||||
if (e.kind === 7) {
|
||||
const replyId = findReply(e)
|
||||
|
||||
if (replyId) {
|
||||
relay.getOrLoadNote(replyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes = async limit => {
|
||||
const events = await relay.filterAlerts($user, limit + 1)
|
||||
const notes = await relay.annotateChunk(events.filter(propEq('kind', 1)))
|
||||
const reactions = await Promise.all(
|
||||
events
|
||||
.filter(e => e.kind === 7)
|
||||
.map(async e => ({
|
||||
...e,
|
||||
person: $people[e.pubkey] || {pubkey: e.pubkey},
|
||||
parent: await relay.findNote(findReply(e)),
|
||||
}))
|
||||
)
|
||||
|
||||
if (events.length <= limit) {
|
||||
const [since, until] = cursor.step()
|
||||
|
||||
relay.pool.loadEvents(
|
||||
[{kinds: [1, 7], '#p': [$user.pubkey], since, until}],
|
||||
onEvent
|
||||
)
|
||||
} else {
|
||||
setTimeout(scroller.check, 300)
|
||||
}
|
||||
|
||||
// Combine likes of a single note
|
||||
const likesById = {}
|
||||
const alerts = notes.filter(e => e.pubkey !== $user.pubkey)
|
||||
for (const reaction of reactions) {
|
||||
if (!likesById[reaction.parent.id]) {
|
||||
likesById[reaction.parent.id] = {...reaction.parent, people: []}
|
||||
}
|
||||
|
||||
likesById[reaction.parent.id].people.push(reaction.person)
|
||||
}
|
||||
|
||||
return sortBy(
|
||||
e => -e.created_at,
|
||||
uniqBy(prop('id'), alerts.concat(Object.values(likesById)))
|
||||
)
|
||||
}
|
||||
|
||||
// Clear notification badge
|
||||
alerts.set({since: now()})
|
||||
</script>
|
||||
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each ($events || []) as e (e.id)}
|
||||
{#if e.kind === 7}
|
||||
<li
|
||||
in:fly={{y: 20}}
|
||||
class="py-2 px-3 flex flex-col gap-2 text-white cursor-pointer transition-all
|
||||
border border-solid border-black hover:border-medium hover:bg-dark"
|
||||
on:click={() => modal.set({note: e.parent})}>
|
||||
<div class="flex gap-2 items-center">
|
||||
<Badge person={e.person} />
|
||||
<span>liked your note.</span>
|
||||
</div>
|
||||
<div class="ml-6 text-light">
|
||||
{ellipsize(e.parent.content, 240)}
|
||||
</div>
|
||||
</li>
|
||||
{#each ($notes || []) as e (e.id)}
|
||||
{#if e.people}
|
||||
<li in:fly={{y: 20}}><Like note={e} /></li>
|
||||
{:else}
|
||||
<li in:fly={{y: 20}}><Note showParent note={e} /></li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
|
||||
{#if $events?.length === 0}
|
||||
{#if $notes?.length === 0}
|
||||
<div in:fly={{y: 20}} class="flex w-full justify-center items-center py-16">
|
||||
<div class="text-center max-w-md">
|
||||
No recent activity found.
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Spinner />
|
||||
{/if}
|
||||
|
@ -1,13 +1,10 @@
|
||||
<script>
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Network from "src/views/notes/Network.svelte"
|
||||
import Global from "src/views/notes/Global.svelte"
|
||||
import {now, timedelta} from 'src/util/misc'
|
||||
import relay, {connections} from 'src/relay'
|
||||
import {connections} from 'src/relay'
|
||||
|
||||
export let activeTab
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import {find} from 'ramda'
|
||||
import {find, take, when, propEq} from 'ramda'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {getLastSync} from 'src/util/misc'
|
||||
import {getTagValues, findReply} from 'src/util/nostr'
|
||||
import {getTagValues} from 'src/util/nostr'
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Notes from "src/views/Notes.svelte"
|
||||
import Notes from "src/partials/Notes.svelte"
|
||||
import {t, dispatch} from 'src/state/dispatch'
|
||||
import {modal} from "src/state/app"
|
||||
import relay, {user, people} from 'src/relay'
|
||||
@ -23,22 +23,7 @@
|
||||
sub = await relay.pool.listenForEvents(
|
||||
'routes/Person',
|
||||
[{kind: [0, 1, 5, 7], authors: [pubkey], since}],
|
||||
async e => {
|
||||
if (e.kind === 1) {
|
||||
const filter = await relay.buildNoteContextFilter(e, {since})
|
||||
|
||||
await relay.pool.loadEvents(filter)
|
||||
}
|
||||
|
||||
if (e.kind === 7) {
|
||||
const replyId = findReply(e)
|
||||
|
||||
if (replyId && !await relay.db.events.get(replyId)) {
|
||||
await relay.pool.loadEvents({kinds: [1], ids: [replyId]})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
})
|
||||
|
||||
@ -52,23 +37,20 @@
|
||||
|
||||
const loadNotes = async limit => {
|
||||
const filter = {kinds: [1], authors: [pubkey]}
|
||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||
|
||||
return relay.annotateChunk(notes.slice(0, limit))
|
||||
return relay.annotateChunk(take(limit, await relay.filterEvents(filter)))
|
||||
}
|
||||
|
||||
const loadLikes = async limit => {
|
||||
const filter = {kinds: [7], authors: [pubkey]}
|
||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||
|
||||
return relay.annotateChunk(notes.slice(0, limit))
|
||||
return relay.annotateChunk(take(limit, await relay.filterEvents(filter)))
|
||||
}
|
||||
|
||||
const loadNetwork = async limit => {
|
||||
const filter = {kinds: [1], authors: getTagValues(getPerson().petnames)}
|
||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||
|
||||
return relay.annotateChunk(notes.slice(0, limit))
|
||||
return relay.annotateChunk(take(limit, await relay.filterEvents(filter)))
|
||||
}
|
||||
|
||||
const setActiveTab = tab => navigate(`/people/${pubkey}/${tab}`)
|
||||
|
@ -76,8 +76,14 @@ export const createScroller = loadMore => {
|
||||
|
||||
requestAnimationFrame(check)
|
||||
|
||||
return () => {
|
||||
done = true
|
||||
return {
|
||||
check: () => {
|
||||
didLoad = false
|
||||
check()
|
||||
},
|
||||
stop: () => {
|
||||
done = true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
59
src/views/Global.svelte
Normal file
59
src/views/Global.svelte
Normal file
@ -0,0 +1,59 @@
|
||||
<script>
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import Notes from "src/views/Notes.svelte"
|
||||
import {now, timedelta, getLastSync} from 'src/util/misc'
|
||||
import relay, {network} from 'src/relay'
|
||||
|
||||
let sub
|
||||
let since = getLastSync('views/Global')
|
||||
|
||||
onMount(async () => {
|
||||
sub = await subscribe(now())
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (sub) {
|
||||
sub.unsub()
|
||||
}
|
||||
})
|
||||
|
||||
const subscribe = until =>
|
||||
relay.pool.listenForEvents(
|
||||
'views/Network',
|
||||
[{kinds: [1, 5, 7], authors: $network, since, until}],
|
||||
async e => {
|
||||
if (e.kind === 1) {
|
||||
const filter = await relay.buildNoteContextFilter(e, {since})
|
||||
|
||||
await relay.pool.loadEvents(filter)
|
||||
}
|
||||
|
||||
if (e.kind === 7) {
|
||||
const replyId = findReply(e)
|
||||
|
||||
if (replyId && !await relay.db.events.get(replyId)) {
|
||||
await relay.pool.loadEvents({kinds: [1], ids: [replyId]})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const loadNotes = async limit => {
|
||||
const filter = {kinds: [1], authors: $network}
|
||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||
|
||||
if (notes.length < limit) {
|
||||
const until = notes.reduce((t, n) => Math.min(n.created_at), since)
|
||||
|
||||
since = until - timedelta(1, 'hours')
|
||||
|
||||
sub = await subscribe(since)
|
||||
}
|
||||
|
||||
return relay.annotateChunk(notes.slice(0, limit))
|
||||
}
|
||||
</script>
|
||||
|
||||
<Notes shouldMuffle loadNotes={loadNotes} />
|
1
src/views/Network.svelte
Normal file
1
src/views/Network.svelte
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import {when, propEq} from 'ramda'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import relay from 'src/relay'
|
||||
import {getLastSync} from 'src/util/misc'
|
||||
import Note from 'src/views/Note.svelte'
|
||||
import Note from 'src/partials/Note.svelte'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
|
||||
export let note
|
||||
@ -12,10 +12,14 @@
|
||||
onMount(async () => {
|
||||
note = await relay.getOrLoadNote(note.id)
|
||||
|
||||
// Log this for debugging purposes
|
||||
console.log('NoteDetail', note)
|
||||
|
||||
if (note) {
|
||||
sub = await relay.pool.listenForEvents(
|
||||
'routes/NoteDetail',
|
||||
await relay.buildNoteContextFilter(note)
|
||||
[{kinds: [1, 5, 7], '#e': [note.id]}],
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -27,14 +31,7 @@
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
observable = relay.lq(async () => {
|
||||
const details = await relay.findNote(note.id, {showEntire: true})
|
||||
|
||||
// Log this for debugging purposes
|
||||
console.log('NoteDetail', details)
|
||||
|
||||
return details
|
||||
})
|
||||
observable = relay.lq(() => relay.findNote(note.id, {showEntire: true}))
|
||||
|
||||
return () => {
|
||||
if (sub) {
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script>
|
||||
import {take} from 'ramda'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import Note from "src/views/Note.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import relay from 'src/relay'
|
||||
|
||||
export let q
|
||||
@ -9,8 +10,7 @@
|
||||
let results = []
|
||||
|
||||
const search = relay.lq(async () => {
|
||||
const notes = await relay.filterEvents({kinds: [1]})
|
||||
.limit(5000).reverse().sortBy('created_at')
|
||||
const notes = take(5000, await relay.filterEvents({kinds: [1]}))
|
||||
|
||||
return fuzzy(notes, {keys: ["content"]})
|
||||
})
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import {when, take, propEq} from 'ramda'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import Notes from "src/views/Notes.svelte"
|
||||
import {now, timedelta, Cursor, getLastSync} from 'src/util/misc'
|
||||
import Notes from "src/partials/Notes.svelte"
|
||||
import {timedelta, Cursor, getLastSync} from 'src/util/misc'
|
||||
import relay from 'src/relay'
|
||||
|
||||
let sub
|
||||
@ -12,28 +12,11 @@
|
||||
timedelta(1, 'minutes')
|
||||
)
|
||||
|
||||
const onEvent = async e => {
|
||||
if (e.kind === 1) {
|
||||
const filter = await relay.buildNoteContextFilter(e)
|
||||
|
||||
await relay.pool.loadEvents(filter)
|
||||
}
|
||||
|
||||
if (e.kind === 7) {
|
||||
const replyId = findReply(e)
|
||||
|
||||
if (replyId && !await relay.db.events.get(replyId)) {
|
||||
await relay.pool.loadEvents({kinds: [1], ids: [replyId]})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
sub = await relay.pool.listenForEvents(
|
||||
'views/notes/Global',
|
||||
[{kinds: [1, 5, 7], since: cursor.since}],
|
||||
onEvent
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
})
|
||||
|
||||
@ -44,12 +27,15 @@
|
||||
})
|
||||
|
||||
const loadNotes = async limit => {
|
||||
const notes = await relay.filterEvents({kinds: [1]}).reverse().sortBy('created_at')
|
||||
const notes = take(limit + 1, await relay.filterEvents({kinds: [1]}))
|
||||
|
||||
if (notes.length < limit) {
|
||||
if (notes.length <= limit) {
|
||||
const [since, until] = cursor.step()
|
||||
|
||||
relay.pool.loadEvents([{kinds: [1, 5, 7], since, until}], onEvent)
|
||||
relay.pool.loadEvents(
|
||||
[{kinds: [1, 5, 7], since, until}],
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
}
|
||||
|
||||
return relay.annotateChunk(notes.slice(0, limit))
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import {when, take, propEq} from 'ramda'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import Notes from "src/views/Notes.svelte"
|
||||
import {now, timedelta, Cursor, getLastSync} from 'src/util/misc'
|
||||
import Notes from "src/partials/Notes.svelte"
|
||||
import {timedelta, Cursor, getLastSync} from 'src/util/misc'
|
||||
import relay, {network} from 'src/relay'
|
||||
|
||||
let sub
|
||||
@ -12,28 +12,11 @@
|
||||
timedelta(1, 'hours')
|
||||
)
|
||||
|
||||
const onEvent = async e => {
|
||||
if (e.kind === 1) {
|
||||
const filter = await relay.buildNoteContextFilter(e)
|
||||
|
||||
await relay.pool.loadEvents(filter)
|
||||
}
|
||||
|
||||
if (e.kind === 7) {
|
||||
const replyId = findReply(e)
|
||||
|
||||
if (replyId && !await relay.db.events.get(replyId)) {
|
||||
await relay.pool.loadEvents({kinds: [1], ids: [replyId]})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
sub = await relay.pool.listenForEvents(
|
||||
'views/notes/Network',
|
||||
[{kinds: [1, 5, 7], authors: $network, since: cursor.since}],
|
||||
onEvent
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
})
|
||||
|
||||
@ -45,14 +28,14 @@
|
||||
|
||||
const loadNotes = async limit => {
|
||||
const filter = {kinds: [1], authors: $network}
|
||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||
const notes = take(limit + 1, await relay.filterEvents(filter))
|
||||
|
||||
if (notes.length < limit) {
|
||||
if (notes.length <= limit) {
|
||||
const [since, until] = cursor.step()
|
||||
|
||||
relay.pool.loadEvents(
|
||||
[{kinds: [1, 5, 7], authors: $network, since, until}],
|
||||
onEvent
|
||||
when(propEq('kind', 1), relay.loadNoteContext)
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user