mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Working on loading
This commit is contained in:
parent
f2d1b0c951
commit
831ef12ab5
@ -32,6 +32,7 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
|||||||
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
||||||
- [ ] Stack views so scroll position isn't lost on navigation
|
- [ ] Stack views so scroll position isn't lost on navigation
|
||||||
- [ ] We're sending client=astral tags, event id 125ff9dc495f65d302e8d95ea6f9385106cc31b81c80e8c582b44be92fa50c44
|
- [ ] We're sending client=astral tags, event id 125ff9dc495f65d302e8d95ea6f9385106cc31b81c80e8c582b44be92fa50c44
|
||||||
|
- [ ] Add notification for slow relays
|
||||||
|
|
||||||
# Curreent update
|
# Curreent update
|
||||||
|
|
||||||
@ -39,12 +40,14 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
|||||||
- [ ] Delete old events
|
- [ ] Delete old events
|
||||||
- [ ] Sync account updates to user for e.g. muffle settings
|
- [ ] Sync account updates to user for e.g. muffle settings
|
||||||
- [ ] Test nos2x
|
- [ ] Test nos2x
|
||||||
|
- [ ] Make sure login/out, no user usage works
|
||||||
|
- [ ] Add a re-sync/clear cache button
|
||||||
- https://vitejs.dev/guide/features.html#web-workers
|
- https://vitejs.dev/guide/features.html#web-workers
|
||||||
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
||||||
- https://web.dev/module-workers/
|
- https://web.dev/module-workers/
|
||||||
|
|
||||||
- [ ] Sync user
|
- [x] Sync user
|
||||||
- [ ] Based on petnames, sync network to 2 or 3 degrees of separation
|
- [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
|
- When a user is added/removed, sync them and add to or remove from network
|
||||||
- [ ] Main fetch requests:
|
- [ ] Main fetch requests:
|
||||||
- Fetch feed by name, since last sync
|
- Fetch feed by name, since last sync
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import "@fortawesome/fontawesome-free/css/solid.css"
|
import "@fortawesome/fontawesome-free/css/solid.css"
|
||||||
|
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {writable} from "svelte/store"
|
import {writable, get} from "svelte/store"
|
||||||
import {fly, fade} from "svelte/transition"
|
import {fly, fade} from "svelte/transition"
|
||||||
import {cubicInOut} from "svelte/easing"
|
import {cubicInOut} from "svelte/easing"
|
||||||
import {throttle} from 'throttle-debounce'
|
import {throttle} from 'throttle-debounce'
|
||||||
@ -13,7 +13,7 @@
|
|||||||
import {timedelta} from 'src/util/misc'
|
import {timedelta} from 'src/util/misc'
|
||||||
import {store as toast} from "src/state/toast"
|
import {store as toast} from "src/state/toast"
|
||||||
import {modal, alerts} from "src/state/app"
|
import {modal, alerts} from "src/state/app"
|
||||||
import relay, {user} from 'src/relay'
|
import relay, {user, connections} from 'src/relay'
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import NoteDetail from "src/views/NoteDetail.svelte"
|
import NoteDetail from "src/views/NoteDetail.svelte"
|
||||||
import PersonSettings from "src/views/PersonSettings.svelte"
|
import PersonSettings from "src/views/PersonSettings.svelte"
|
||||||
@ -52,22 +52,34 @@
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
// Give any animations a moment to finish
|
// Give any animations a moment to finish
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
const $connections = get(connections)
|
||||||
|
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
relay.db.delete()
|
|
||||||
|
// Keep relays around
|
||||||
|
relay.db.events.clear()
|
||||||
|
relay.db.tags.clear()
|
||||||
|
|
||||||
|
// Remember the user's relay selection
|
||||||
|
connections.set($connections)
|
||||||
|
|
||||||
// Do a hard refresh so everything gets totally cleared
|
// Do a hard refresh so everything gets totally cleared
|
||||||
window.location = '/login'
|
window.location = '/login'
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close menu on click outside
|
if ($user) {
|
||||||
document.querySelector("html").addEventListener("click", e => {
|
relay.pool.syncNetwork()
|
||||||
if (e.target !== menuIcon) {
|
}
|
||||||
menuIsOpen.set(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Close menu on click outside
|
||||||
|
document.querySelector("html").addEventListener("click", e => {
|
||||||
|
if (e.target !== menuIcon) {
|
||||||
|
menuIsOpen.set(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return modal.subscribe($modal => {
|
return modal.subscribe($modal => {
|
||||||
// Keep scroll position on body, but don't allow scrolling
|
// Keep scroll position on body, but don't allow scrolling
|
||||||
if ($modal) {
|
if ($modal) {
|
||||||
@ -137,7 +149,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="cursor-pointer">
|
<li class="cursor-pointer">
|
||||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes/global">
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes/network">
|
||||||
<i class="fa-solid fa-tag mr-2" /> Notes
|
<i class="fa-solid fa-tag mr-2" /> Notes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -18,20 +18,19 @@ window.db = db
|
|||||||
// Some things work better as observables than database tables
|
// Some things work better as observables than database tables
|
||||||
|
|
||||||
db.user = writable(getLocalJson("db/user"))
|
db.user = writable(getLocalJson("db/user"))
|
||||||
db.people = writable(getLocalJson('db.people') || {})
|
db.people = writable(getLocalJson('db/people') || {})
|
||||||
db.network = writable(getLocalJson('db/network') || [])
|
db.network = writable(getLocalJson('db/network') || [])
|
||||||
db.connections = writable(getLocalJson("db/connections") || [])
|
db.connections = writable(getLocalJson("db/connections") || [])
|
||||||
|
|
||||||
db.user.subscribe($user => setLocalJson("coracle/user", $user))
|
db.user.subscribe($user => setLocalJson("db/user", $user))
|
||||||
db.people.subscribe($people => setLocalJson("coracle/people", $people))
|
db.people.subscribe($people => setLocalJson("db/people", $people))
|
||||||
db.network.subscribe($network => setLocalJson("coracle/network", $network))
|
db.network.subscribe($network => setLocalJson("db/network", $network))
|
||||||
db.connections.subscribe($connections => setLocalJson("coracle/connections", $connections))
|
db.connections.subscribe($connections => setLocalJson("db/connections", $connections))
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
||||||
db.events.process = async events => {
|
db.events.process = async events => {
|
||||||
// Only persist ones we care about, the rest can be
|
// Only persist ones we care about, the rest can be ephemeral and used to update people etc
|
||||||
// ephemeral and used to update people etc
|
|
||||||
const eventsByKind = groupBy(prop('kind'), ensurePlural(events))
|
const eventsByKind = groupBy(prop('kind'), ensurePlural(events))
|
||||||
const notesAndReactions = flatten(Object.values(pick([1, 7], eventsByKind)))
|
const notesAndReactions = flatten(Object.values(pick([1, 7], eventsByKind)))
|
||||||
const profileUpdates = flatten(Object.values(pick([0, 3, 12165], eventsByKind)))
|
const profileUpdates = flatten(Object.values(pick([0, 3, 12165], eventsByKind)))
|
||||||
@ -40,7 +39,7 @@ db.events.process = async events => {
|
|||||||
// Persist notes and reactions
|
// Persist notes and reactions
|
||||||
if (notesAndReactions.length > 0) {
|
if (notesAndReactions.length > 0) {
|
||||||
const persistentEvents = notesAndReactions
|
const persistentEvents = notesAndReactions
|
||||||
.map(e => ({...e, root: findRoot(e), reply: findReply(e)}))
|
.map(e => ({...e, root: findRoot(e), reply: findReply(e), added_at: now()}))
|
||||||
|
|
||||||
db.events.bulkPut(persistentEvents)
|
db.events.bulkPut(persistentEvents)
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {liveQuery} from 'dexie'
|
import {liveQuery} from 'dexie'
|
||||||
import {pluck, without, uniqBy, prop, groupBy, concat, uniq, objOf, isNil, identity} from 'ramda'
|
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 {ensurePlural, createMap, ellipsize, first} from 'hurdak/lib/hurdak'
|
||||||
import {escapeHtml} from 'src/util/html'
|
import {escapeHtml} from 'src/util/html'
|
||||||
import {filterTags, findRoot, findReply} from 'src/util/nostr'
|
import {filterTags, findReply, findRoot} from 'src/util/nostr'
|
||||||
import {db} from 'src/relay/db'
|
import {db} from 'src/relay/db'
|
||||||
import pool from 'src/relay/pool'
|
import pool from 'src/relay/pool'
|
||||||
|
|
||||||
@ -15,12 +16,25 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
// Context getters attempt to retrieve from the db and fall back to the network
|
// Context getters attempt to retrieve from the db and fall back to the network
|
||||||
|
|
||||||
const ensurePerson = async ({pubkey}) => {
|
const ensurePerson = async ({pubkey}) => {
|
||||||
const person = await db.people.where('pubkey').equals(pubkey).first()
|
await pool.syncPersonInfo({...prop(pubkey, get(db.people)), pubkey})
|
||||||
|
|
||||||
await pool.syncPersonInfo({pubkey, ...person})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureContext = async events => {
|
const ensureContext = async events => {
|
||||||
@ -80,6 +94,71 @@ const filterEvents = filter => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
return replies
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
return reactions
|
||||||
|
}
|
||||||
|
|
||||||
|
const findReaction = async (id, filter) =>
|
||||||
|
first(await filterReactions(id, filter))
|
||||||
|
|
||||||
|
const countReactions = async (id, filter) =>
|
||||||
|
(await filterReactions(id, filter)).length
|
||||||
|
|
||||||
|
const getOrLoadNote = async (id, {showEntire = false} = {}) => {
|
||||||
|
const note = await db.events.get(id)
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
return first(await pool.loadEvents({kinds: [1], ids: [id]}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
const findNote = async (id, {showEntire = false} = {}) => {
|
||||||
|
const note = await db.events.get(id)
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactions = await filterReactions(note.id)
|
||||||
|
const replies = await filterReplies(note.id)
|
||||||
|
const person = prop(note.pubkey, get(db.people))
|
||||||
|
const html = await renderNote(note, {showEntire})
|
||||||
|
|
||||||
|
let parent = null
|
||||||
|
const parentId = findReply(note)
|
||||||
|
if (parentId) {
|
||||||
|
parent = await db.events.get(parentId)
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent = {
|
||||||
|
...parent,
|
||||||
|
reactions: await filterReactions(parent.id),
|
||||||
|
person: prop(parent.pubkey, get(db.people)),
|
||||||
|
html: await renderNote(parent, {showEntire}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...note, reactions, person, html, parent,
|
||||||
|
replies: await Promise.all(replies.map(r => findNote(r.id))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const annotateChunk = async chunk => {
|
const annotateChunk = async chunk => {
|
||||||
const ancestorIds = concat(chunk.map(findRoot), chunk.map(findReply)).filter(identity)
|
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}).toArray()
|
||||||
@ -108,66 +187,15 @@ const annotateChunk = async chunk => {
|
|||||||
return await Promise.all(Object.keys(notesByRoot).map(findNote))
|
return await Promise.all(Object.keys(notesByRoot).map(findNote))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
return replies
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
return reactions
|
|
||||||
}
|
|
||||||
|
|
||||||
const findReaction = async (id, filter) =>
|
|
||||||
first(await filterReactions(id, filter))
|
|
||||||
|
|
||||||
const countReactions = async (id, filter) =>
|
|
||||||
(await filterReactions(id, filter)).length
|
|
||||||
|
|
||||||
const findNote = async (id, {giveUp = false, showEntire = 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.warn(`Failed to find context for note ${id}`)
|
|
||||||
|
|
||||||
if (giveUp) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
await ensureContext(await pool.loadEvents({ids: [id]}))
|
|
||||||
|
|
||||||
return findNote(id, {giveUp: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
const [replies, reactions, person, html] = await Promise.all([
|
|
||||||
children.clone().filter(e => e.kind === 1).toArray(),
|
|
||||||
children.clone().filter(e => e.kind === 7).toArray(),
|
|
||||||
db.people.get(note.pubkey),
|
|
||||||
renderNote(note, {showEntire}),
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
|
||||||
...note, reactions, person, html,
|
|
||||||
replies: await Promise.all(replies.map(r => findNote(r.id))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderNote = async (note, {showEntire = false}) => {
|
const renderNote = async (note, {showEntire = false}) => {
|
||||||
|
const $people = get(db.people)
|
||||||
|
|
||||||
const shouldEllipsize = note.content.length > 500 && !showEntire
|
const shouldEllipsize = note.content.length > 500 && !showEntire
|
||||||
const content = shouldEllipsize ? ellipsize(note.content, 500) : note.content
|
const content = shouldEllipsize ? ellipsize(note.content, 500) : note.content
|
||||||
const people = await db.people.where('pubkey').anyOf(filterTags({tag: "p"}, note)).toArray()
|
const peopleByPubkey = createMap(
|
||||||
const peopleByPubkey = createMap('pubkey', people)
|
'pubkey',
|
||||||
|
filterTags({tag: "p"}, note).map(k => $people[k]).filter(identity)
|
||||||
|
)
|
||||||
|
|
||||||
return escapeHtml(content)
|
return escapeHtml(content)
|
||||||
.replace(/\n/g, '<br />')
|
.replace(/\n/g, '<br />')
|
||||||
@ -199,13 +227,14 @@ const filterAlerts = async (person, filter) => {
|
|||||||
|
|
||||||
const login = ({privkey, pubkey}) => {
|
const login = ({privkey, pubkey}) => {
|
||||||
db.user.set({relays: [], muffle: [], petnames: [], updated_at: 0, pubkey, privkey})
|
db.user.set({relays: [], muffle: [], petnames: [], updated_at: 0, pubkey, privkey})
|
||||||
|
|
||||||
|
pool.syncNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRelay = url => {
|
const addRelay = url => {
|
||||||
db.connections.update($connections => $connections.concat(url))
|
db.connections.update($connections => $connections.concat(url))
|
||||||
|
|
||||||
pool.syncNetwork()
|
pool.syncNetwork()
|
||||||
pool.syncNetworkNotes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeRelay = url => {
|
const removeRelay = url => {
|
||||||
@ -216,7 +245,6 @@ const follow = async pubkey => {
|
|||||||
db.network.update($network => $network.concat(pubkey))
|
db.network.update($network => $network.concat(pubkey))
|
||||||
|
|
||||||
pool.syncNetwork()
|
pool.syncNetwork()
|
||||||
pool.syncNetworkNotes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfollow = async pubkey => {
|
const unfollow = async pubkey => {
|
||||||
@ -255,7 +283,8 @@ export const network = db.network
|
|||||||
export const connections = db.connections
|
export const connections = db.connections
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
db, pool, lq, ensurePerson, ensureContext, filterEvents, filterReactions,
|
db, pool, lq, buildNoteContextFilter, ensurePerson, ensureContext, filterEvents,
|
||||||
countReactions, findReaction, filterReplies, findNote, renderNote, filterAlerts,
|
filterReactions, getOrLoadNote,
|
||||||
annotateChunk, login, addRelay, removeRelay, follow, unfollow,
|
countReactions, findReaction, filterReplies, findNote, annotateChunk, renderNote,
|
||||||
|
filterAlerts, login, addRelay, removeRelay, follow, unfollow,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import {uniqBy, prop, uniq} from 'ramda'
|
|||||||
import {get} from 'svelte/store'
|
import {get} from 'svelte/store'
|
||||||
import {relayPool, getPublicKey} from 'nostr-tools'
|
import {relayPool, getPublicKey} from 'nostr-tools'
|
||||||
import {noop, range} from 'hurdak/lib/hurdak'
|
import {noop, range} from 'hurdak/lib/hurdak'
|
||||||
import {now, timedelta, randomChoice} from "src/util/misc"
|
import {now, timedelta, randomChoice, getLocalJson, setLocalJson} from "src/util/misc"
|
||||||
import {getTagValues, filterTags} from "src/util/nostr"
|
import {getTagValues, filterTags} from "src/util/nostr"
|
||||||
import {db} from 'src/relay/db'
|
import {db} from 'src/relay/db'
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ class Channel {
|
|||||||
this.name = name
|
this.name = name
|
||||||
this.p = Promise.resolve()
|
this.p = Promise.resolve()
|
||||||
}
|
}
|
||||||
async sub(filter, onEvent, onEose = noop) {
|
async sub(filter, onEvent, onEose = noop, timeout = 30000) {
|
||||||
// If we don't have any relays, we'll wait forever for an eose, but
|
// If we don't have any relays, we'll wait forever for an eose, but
|
||||||
// we already know we're done. Use a timeout since callers are
|
// we already know we're done. Use a timeout since callers are
|
||||||
// expecting this to be async and we run into errors otherwise.
|
// expecting this to be async and we run into errors otherwise.
|
||||||
@ -37,18 +37,23 @@ class Channel {
|
|||||||
// before they can get a new one.
|
// before they can get a new one.
|
||||||
await p
|
await p
|
||||||
|
|
||||||
// Start our subscription, wait for only one relays to eose before
|
// Start our subscription, wait for only one relay to eose before
|
||||||
// calling it done. We were waiting for all before, but that made
|
// calling it done. We were waiting for all before, but that made
|
||||||
// the slowest relay a bottleneck
|
// the slowest relay a bottleneck
|
||||||
const sub = pool.sub({filter, cb: onEvent}, this.name, onEose)
|
const sub = pool.sub({filter, cb: onEvent}, this.name, onEose)
|
||||||
|
|
||||||
return {
|
const done = () => {
|
||||||
unsub: () => {
|
sub.unsub()
|
||||||
sub.unsub()
|
|
||||||
|
|
||||||
resolve()
|
resolve()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the relay takes to long, just give up
|
||||||
|
if (timeout) {
|
||||||
|
setTimeout(done, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {unsub: done}
|
||||||
}
|
}
|
||||||
all(filter) {
|
all(filter) {
|
||||||
/* eslint no-async-promise-executor: 0 */
|
/* eslint no-async-promise-executor: 0 */
|
||||||
@ -119,12 +124,18 @@ const loadEvents = async filter => {
|
|||||||
|
|
||||||
const subs = {}
|
const subs = {}
|
||||||
|
|
||||||
const listenForEvents = async (key, filter) => {
|
const listenForEvents = async (key, filter, onEvent) => {
|
||||||
if (subs[key]) {
|
if (subs[key]) {
|
||||||
subs[key].unsub()
|
subs[key].unsub()
|
||||||
}
|
}
|
||||||
|
|
||||||
subs[key] = await sub(filter, db.events.process)
|
subs[key] = await sub(filter, e => {
|
||||||
|
db.events.process(e)
|
||||||
|
|
||||||
|
if (onEvent) {
|
||||||
|
onEvent(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadPeople = pubkeys => {
|
const loadPeople = pubkeys => {
|
||||||
@ -145,8 +156,6 @@ const syncNetwork = async () => {
|
|||||||
// Merge the new info into our user
|
// Merge the new info into our user
|
||||||
Object.assign($user, people[$user.pubkey])
|
Object.assign($user, people[$user.pubkey])
|
||||||
|
|
||||||
console.log($user)
|
|
||||||
|
|
||||||
// Update our user store
|
// Update our user store
|
||||||
db.user.update(() => $user)
|
db.user.update(() => $user)
|
||||||
|
|
||||||
@ -161,27 +170,29 @@ const syncNetwork = async () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let networkPubkeys = pubkeys
|
let authors = pubkeys
|
||||||
for (let depth = 0; depth < 1; depth++) {
|
for (let depth = 0; depth < 1; depth++) {
|
||||||
const events = await loadPeople(pubkeys)
|
const events = await loadPeople(pubkeys)
|
||||||
|
|
||||||
pubkeys = uniq(filterTags({type: "p"}, events.filter(e => e.kind === 3)))
|
pubkeys = uniq(filterTags({type: "p"}, events.filter(e => e.kind === 3)))
|
||||||
|
|
||||||
networkPubkeys = networkPubkeys.concat(pubkeys)
|
authors = authors.concat(pubkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.network.set(networkPubkeys)
|
// Save this for next time
|
||||||
}
|
db.network.set(authors)
|
||||||
|
|
||||||
const syncNetworkNotes = () => {
|
// Grab everything since our most recent sync
|
||||||
const authors = get(db.network)
|
const since = getLocalJson('syncNetwork/lastSync') || now() - timedelta(30, 'days')
|
||||||
const since = now() - timedelta(30, 'days')
|
|
||||||
|
|
||||||
loadEvents({kinds: [1, 5, 7], authors, since, until: now()})
|
loadEvents({kinds: [1, 5, 7], authors, since, until: now()})
|
||||||
listenForEvents('networkNotes', {kinds: [1, 5, 7], authors, since: now()})
|
listenForEvents('pool/networkNotes', {kinds: [1, 5, 7], authors, since: now()})
|
||||||
|
|
||||||
|
// Save our position to speed up next page load
|
||||||
|
setLocalJson('syncNetwork/lastSync', now() - timedelta(5, 'minutes'))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getPubkey, getRelays, addRelay, removeRelay, setPrivateKey, setPublicKey,
|
getPubkey, getRelays, addRelay, removeRelay, setPrivateKey, setPublicKey,
|
||||||
publishEvent, loadEvents, syncNetwork, syncNetworkNotes,
|
publishEvent, loadEvents, listenForEvents, syncNetwork,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import toast from 'src/state/toast'
|
import toast from 'src/state/toast'
|
||||||
import {modal} from 'src/state/app'
|
import {modal} from 'src/state/app'
|
||||||
import {dispatch} from 'src/state/dispatch'
|
|
||||||
import Input from 'src/partials/Input.svelte'
|
import Input from 'src/partials/Input.svelte'
|
||||||
import Button from 'src/partials/Button.svelte'
|
import Button from 'src/partials/Button.svelte'
|
||||||
import relay from 'src/relay'
|
import relay from 'src/relay'
|
||||||
@ -17,7 +16,7 @@
|
|||||||
return toast.show("error", 'That isn\'t a valid websocket url - relay urls should start with "wss://"')
|
return toast.show("error", 'That isn\'t a valid websocket url - relay urls should start with "wss://"')
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.db.relays.put(url)
|
relay.db.relays.put({url})
|
||||||
relay.addRelay(url)
|
relay.addRelay(url)
|
||||||
modal.set(null)
|
modal.set(null)
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
import {onMount} from 'svelte'
|
import {onMount} from 'svelte'
|
||||||
import {navigate} from 'svelte-routing'
|
import {navigate} from 'svelte-routing'
|
||||||
|
|
||||||
onMount(() => navigate('/notes/global'))
|
onMount(() => navigate('/notes/network'))
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,15 +1,72 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {onMount, onDestroy} from 'svelte'
|
||||||
import {navigate} from 'svelte-routing'
|
import {navigate} from 'svelte-routing'
|
||||||
|
import {findReply} from 'src/util/nostr'
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Tabs from "src/partials/Tabs.svelte"
|
import Tabs from "src/partials/Tabs.svelte"
|
||||||
import Notes from "src/views/Notes.svelte"
|
import Notes from "src/views/Notes.svelte"
|
||||||
import {timedelta} from 'src/util/misc'
|
import {now, timedelta} from 'src/util/misc'
|
||||||
import relay, {user, connections} from 'src/relay'
|
import relay, {network, connections} from 'src/relay'
|
||||||
|
|
||||||
export let activeTab
|
export let activeTab
|
||||||
|
|
||||||
const authors = $user ? $user.petnames.map(t => t[1]) : []
|
let sub
|
||||||
|
let delta = timedelta(1, 'minutes')
|
||||||
|
let since = now() - delta
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
sub = await subscribe(now())
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (sub) {
|
||||||
|
sub.unsub()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const setActiveTab = tab => navigate(`/notes/${tab}`)
|
const setActiveTab = tab => navigate(`/notes/${tab}`)
|
||||||
|
|
||||||
|
const subscribe = until =>
|
||||||
|
relay.pool.listenForEvents(
|
||||||
|
'routes/Notes',
|
||||||
|
[{kinds: [1, 5, 7], 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 loadNetworkNotes = async limit => {
|
||||||
|
const filter = {kinds: [1], authors: $network}
|
||||||
|
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||||
|
|
||||||
|
return relay.annotateChunk(notes.slice(0, limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadGlobalNotes = async limit => {
|
||||||
|
const filter = {kinds: [1], since}
|
||||||
|
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
||||||
|
|
||||||
|
if (notes.length < limit) {
|
||||||
|
since -= delta
|
||||||
|
|
||||||
|
sub = await subscribe(since + delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
return relay.annotateChunk(notes.slice(0, limit))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $connections.length === 0}
|
{#if $connections.length === 0}
|
||||||
@ -21,17 +78,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Tabs tabs={['global', 'follows']} {activeTab} {setActiveTab} />
|
<Tabs tabs={['network', 'global']} {activeTab} {setActiveTab} />
|
||||||
{#if activeTab === 'follows' && authors.length === 0}
|
{#if activeTab === 'network'}
|
||||||
<div class="flex w-full justify-center items-center py-16">
|
<Notes shouldMuffle loadNotes={loadNetworkNotes} />
|
||||||
<div class="text-center max-w-md">
|
|
||||||
You haven't yet followed anyone. Visit a person's profile to follow them.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if activeTab === 'follows'}
|
|
||||||
<Notes filter={{kinds: [1], authors}} shouldMuffle />
|
|
||||||
{:else}
|
{:else}
|
||||||
<Notes delta={timedelta(5, 'minutes')} filter={{kinds: [1]}} shouldMuffle />
|
<Notes shouldMuffle loadNotes={loadGlobalNotes} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="fixed bottom-0 right-0 p-8">
|
<div class="fixed bottom-0 right-0 p-8">
|
||||||
<a
|
<a
|
||||||
|
@ -1,31 +1,81 @@
|
|||||||
<script>
|
<script>
|
||||||
import {find} from 'ramda'
|
import {find} from 'ramda'
|
||||||
|
import {onMount, onDestroy} from 'svelte'
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {navigate} from 'svelte-routing'
|
import {navigate} from 'svelte-routing'
|
||||||
import {timedelta} from 'src/util/misc'
|
import {getLastSync} from 'src/util/misc'
|
||||||
|
import {getTagValues, findReply} from 'src/util/nostr'
|
||||||
import Tabs from "src/partials/Tabs.svelte"
|
import Tabs from "src/partials/Tabs.svelte"
|
||||||
import Button from "src/partials/Button.svelte"
|
import Button from "src/partials/Button.svelte"
|
||||||
import Notes from "src/views/Notes.svelte"
|
import Notes from "src/views/Notes.svelte"
|
||||||
import Likes from "src/views/Likes.svelte"
|
|
||||||
import {t, dispatch} from 'src/state/dispatch'
|
import {t, dispatch} from 'src/state/dispatch'
|
||||||
import {modal} from "src/state/app"
|
import {modal} from "src/state/app"
|
||||||
import relay from 'src/relay'
|
import relay, {user, people} from 'src/relay'
|
||||||
import {user} from "src/relay"
|
|
||||||
|
|
||||||
export let pubkey
|
export let pubkey
|
||||||
export let activeTab
|
export let activeTab
|
||||||
|
|
||||||
relay.ensurePerson({pubkey})
|
let sub = null
|
||||||
|
|
||||||
const person = relay.lq(() => relay.db.people.get(pubkey))
|
|
||||||
|
|
||||||
let following = $user && find(t => t[1] === pubkey, $user.petnames)
|
let following = $user && find(t => t[1] === pubkey, $user.petnames)
|
||||||
|
let since = getLastSync(['Person', pubkey])
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
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]})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (sub) {
|
||||||
|
sub.unsub()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getPerson = () => $people[pubkey]
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
const setActiveTab = tab => navigate(`/people/${pubkey}/${tab}`)
|
const setActiveTab = tab => navigate(`/people/${pubkey}/${tab}`)
|
||||||
|
|
||||||
const follow = () => {
|
const follow = () => {
|
||||||
const petnames = $user.petnames
|
const petnames = $user.petnames
|
||||||
.concat([t("p", pubkey, $person?.name)])
|
.concat([t("p", pubkey, getPerson()?.name)])
|
||||||
|
|
||||||
dispatch('user/petnames', petnames)
|
dispatch('user/petnames', petnames)
|
||||||
|
|
||||||
@ -42,7 +92,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openAdvanced = () => {
|
const openAdvanced = () => {
|
||||||
modal.set({form: 'person/settings', person: $person || {pubkey}})
|
modal.set({form: 'person/settings', person: getPerson() || {pubkey}})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -51,15 +101,15 @@
|
|||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||||
style="background-image: url({$person?.picture})" />
|
style="background-image: url({getPerson()?.picture})" />
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1 class="text-2xl">{$person?.name || pubkey.slice(0, 8)}</h1>
|
<h1 class="text-2xl">{getPerson()?.name || pubkey.slice(0, 8)}</h1>
|
||||||
{#if $user && $user.pubkey !== pubkey}
|
{#if $user && $user.pubkey !== pubkey}
|
||||||
<i class="fa-solid fa-sliders cursor-pointer" on:click={openAdvanced} />
|
<i class="fa-solid fa-sliders cursor-pointer" on:click={openAdvanced} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p>{$person?.about || ''}</p>
|
<p>{getPerson()?.about || ''}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="whitespace-nowrap">
|
<div class="whitespace-nowrap">
|
||||||
{#if $user?.pubkey === pubkey}
|
{#if $user?.pubkey === pubkey}
|
||||||
@ -82,12 +132,12 @@
|
|||||||
|
|
||||||
<Tabs tabs={['notes', 'likes', 'network']} {activeTab} {setActiveTab} />
|
<Tabs tabs={['notes', 'likes', 'network']} {activeTab} {setActiveTab} />
|
||||||
{#if activeTab === 'notes'}
|
{#if activeTab === 'notes'}
|
||||||
<Notes showParent delta={timedelta(1, 'days')} filter={{kinds: [1], authors: [pubkey]}} />
|
<Notes showParent loadNotes={loadNotes} />
|
||||||
{:else if activeTab === 'likes'}
|
{:else if activeTab === 'likes'}
|
||||||
<Likes author={pubkey} />
|
<Notes loadNotes={loadLikes} />
|
||||||
{:else if activeTab === 'network'}
|
{:else if activeTab === 'network'}
|
||||||
{#if $person}
|
{#if getPerson()}
|
||||||
<Notes shouldMuffle filter={{kinds: [1], authors: $person.petnames.map(t => t[1])}} />
|
<Notes shouldMuffle loadNotes={loadNetwork} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="py-16 max-w-xl m-auto flex justify-center">
|
<div class="py-16 max-w-xl m-auto flex justify-center">
|
||||||
Unable to show network for this person.
|
Unable to show network for this person.
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
settings.set(values)
|
settings.set(values)
|
||||||
|
|
||||||
navigate('/notes/global')
|
navigate('/notes/network')
|
||||||
|
|
||||||
toast.show("info", "Your settings have been saved!")
|
toast.show("info", "Your settings have been saved!")
|
||||||
}
|
}
|
||||||
|
@ -85,12 +85,15 @@ export const copyTags = (e, newTags = []) => {
|
|||||||
// Remove reply type from e tags
|
// Remove reply type from e tags
|
||||||
return uniqBy(
|
return uniqBy(
|
||||||
t => t.join(':'),
|
t => t.join(':'),
|
||||||
e.tags.map(t => last(t) === 'reply' ? t.slice(0, -1) : t).concat(newTags)
|
e.tags
|
||||||
|
.filter(t => ["p", "e"].includes(t[0]))
|
||||||
|
.map(t => last(t) === 'reply' ? t.slice(0, -1) : t)
|
||||||
|
.concat(newTags)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const t = (type, content, marker) => {
|
export const t = (type, content, marker) => {
|
||||||
const tag = [type, content, first(Object.keys(relay.pool.relays))]
|
const tag = [type, content, first(Object.keys(relay.pool.getRelays()))]
|
||||||
|
|
||||||
if (!isNil(marker)) {
|
if (!isNil(marker)) {
|
||||||
tag.push(marker)
|
tag.push(marker)
|
||||||
|
@ -64,7 +64,10 @@ export const createScroller = loadMore => {
|
|||||||
await loadMore()
|
await loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(1000)
|
// This is a gross hack, basically, keep loading if the user doesn't scroll again,
|
||||||
|
// but wait a long time because otherwise we'll send off multiple concurrent requests
|
||||||
|
// that will clog up our channels and stall the app.
|
||||||
|
await sleep(30000)
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
requestAnimationFrame(check)
|
requestAnimationFrame(check)
|
||||||
@ -79,3 +82,12 @@ export const createScroller = loadMore => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const randomChoice = xs => xs[Math.floor(Math.random() * xs.length)]
|
export const randomChoice = xs => xs[Math.floor(Math.random() * xs.length)]
|
||||||
|
|
||||||
|
export const getLastSync = (keyParts, fallback) => {
|
||||||
|
const key = `${keyParts.join('.')}/lastSync`
|
||||||
|
const lastSync = getLocalJson(key) || fallback
|
||||||
|
|
||||||
|
setLocalJson(key, now())
|
||||||
|
|
||||||
|
return lastSync
|
||||||
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
<script>
|
|
||||||
import {onDestroy} from 'svelte'
|
|
||||||
import {fly} from 'svelte/transition'
|
|
||||||
import {uniqBy, identity, prop} from 'ramda'
|
|
||||||
import {timedelta} from 'src/util/misc'
|
|
||||||
import Note from "src/views/Note.svelte"
|
|
||||||
import {findReply} from 'src/util/nostr'
|
|
||||||
import relay from 'src/relay'
|
|
||||||
|
|
||||||
export let author
|
|
||||||
|
|
||||||
const filter = {kinds: [7], authors: [author]}
|
|
||||||
const delta = timedelta(1, 'days')
|
|
||||||
|
|
||||||
let notes
|
|
||||||
|
|
||||||
onDestroy(relay.scroller(filter, delta, async chunk => {
|
|
||||||
notes = relay.lq(async () => {
|
|
||||||
const notes = await Promise.all(chunk.map(r => relay.findNote(findReply(r))))
|
|
||||||
|
|
||||||
return uniqBy(prop('id'), notes.filter(identity))
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $notes}
|
|
||||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
|
||||||
{#each $notes as n (n.id)}
|
|
||||||
<li><Note note={n} depth={1} /></li>
|
|
||||||
{:else}
|
|
||||||
<li class="p-20 text-center" in:fly={{y: 20}}>No notes found.</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
|||||||
let likes, flags, like, flag
|
let likes, flags, like, flag
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
likes = note.reactions.filter(whereEq({content: '+'}))
|
likes = note.reactions.filter(n => ['', '+'].includes(n.content))
|
||||||
flags = note.reactions.filter(whereEq({content: '-'}))
|
flags = note.reactions.filter(whereEq({content: '-'}))
|
||||||
like = find(whereEq({pubkey: $user?.pubkey}), likes)
|
like = find(whereEq({pubkey: $user?.pubkey}), likes)
|
||||||
flag = find(whereEq({pubkey: $user?.pubkey}), flags)
|
flag = find(whereEq({pubkey: $user?.pubkey}), flags)
|
||||||
|
@ -1,14 +1,58 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {onMount, onDestroy} from 'svelte'
|
||||||
import relay from 'src/relay'
|
import relay from 'src/relay'
|
||||||
|
import {getLastSync} from 'src/util/misc'
|
||||||
import Note from 'src/views/Note.svelte'
|
import Note from 'src/views/Note.svelte'
|
||||||
|
import Spinner from 'src/partials/Spinner.svelte'
|
||||||
|
|
||||||
export let note
|
export let note
|
||||||
|
|
||||||
const observable = relay.lq(() => relay.findNote(note.id, {showEntire: true}))
|
let observable, sub
|
||||||
|
let since = getLastSync(['NoteDetail', note.id])
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
note = await relay.getOrLoadNote(note.id)
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
sub = await relay.pool.listenForEvents(
|
||||||
|
'routes/NoteDetail',
|
||||||
|
await relay.buildNoteContextFilter(note, {since})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (sub) {
|
||||||
|
sub.unsub()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (sub) {
|
||||||
|
sub.unsub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $observable}
|
{#if !note}
|
||||||
|
<div class="text-white">
|
||||||
|
Sorry, we weren't able to find this note.
|
||||||
|
</div>
|
||||||
|
{:else if $observable}
|
||||||
<div n:fly={{y: 20}}>
|
<div n:fly={{y: 20}}>
|
||||||
<Note showParent invertColors anchorId={note.id} note={$observable} depth={2} />
|
<Note showParent invertColors anchorId={note.id} note={$observable} depth={2} />
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Spinner />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import {onDestroy} from 'svelte'
|
import {onDestroy} from 'svelte'
|
||||||
import {prop, identity, concat, uniqBy, groupBy} from 'ramda'
|
|
||||||
import {createMap} from 'hurdak/lib/hurdak'
|
|
||||||
import {createScroller} from 'src/util/misc'
|
import {createScroller} from 'src/util/misc'
|
||||||
import {findReply, findRoot} from 'src/util/nostr'
|
|
||||||
import Spinner from 'src/partials/Spinner.svelte'
|
import Spinner from 'src/partials/Spinner.svelte'
|
||||||
import Note from "src/views/Note.svelte"
|
import Note from "src/views/Note.svelte"
|
||||||
import relay from 'src/relay'
|
import relay from 'src/relay'
|
||||||
|
|
||||||
export let filter
|
export let loadNotes
|
||||||
export let showParent = false
|
export let showParent = false
|
||||||
|
|
||||||
let notes
|
let notes
|
||||||
@ -17,35 +14,7 @@
|
|||||||
onDestroy(createScroller(async () => {
|
onDestroy(createScroller(async () => {
|
||||||
limit += 20
|
limit += 20
|
||||||
|
|
||||||
notes = relay.lq(async () => {
|
notes = relay.lq(() => loadNotes(limit))
|
||||||
const notes = await relay.filterEvents(filter).reverse().sortBy('created_at')
|
|
||||||
const chunk = notes.slice(0, limit)
|
|
||||||
const ancestorIds = concat(chunk.map(findRoot), chunk.map(findReply)).filter(identity)
|
|
||||||
const ancestors = await relay.filterEvents({kinds: [1], ids: ancestorIds}).toArray()
|
|
||||||
|
|
||||||
const allNotes = uniqBy(prop('id'), chunk.concat(ancestors))
|
|
||||||
const notesById = createMap('id', allNotes)
|
|
||||||
const notesByRoot = groupBy(
|
|
||||||
n => {
|
|
||||||
const rootId = findRoot(n)
|
|
||||||
const parentId = findReply(n)
|
|
||||||
|
|
||||||
// Actually dereference the notes in case we weren't able to retrieve them
|
|
||||||
if (notesById[rootId]) {
|
|
||||||
return rootId
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notesById[parentId]) {
|
|
||||||
return parentId
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.id
|
|
||||||
},
|
|
||||||
allNotes
|
|
||||||
)
|
|
||||||
|
|
||||||
return await Promise.all(Object.keys(notesByRoot).map(relay.findNote))
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user