mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add likes/network tabs
This commit is contained in:
parent
70ca44226c
commit
65c8f63721
@ -1,10 +1,13 @@
|
||||
Bugs
|
||||
|
||||
- [ ] Permalink note detail (share/permalink button?)
|
||||
- [ ] Back button is pretty broken
|
||||
- [ ] Permalink note detail (share/permalink button?). Permalinks don't work
|
||||
- [ ] Prevent tabs from re-mounting (or at least re- animating)
|
||||
- [ ] Go "back" after adding a note
|
||||
- [ ] uniq and sortBy are sprinkled all over the place, figure out a better solution
|
||||
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
||||
- [ ] Search page is slow and likes don't show up. Probably move this server-side
|
||||
- [ ] Replies counts aren't showing on replies
|
||||
|
||||
Features
|
||||
|
||||
|
@ -102,7 +102,7 @@
|
||||
{/key}
|
||||
</Route>
|
||||
<Route path="/chat/:room/edit" component={ChatEdit} />
|
||||
<Route path="/users/:pubkey" let:params>
|
||||
<Route path="/users/:pubkey/:activeTab" let:params>
|
||||
{#key params.pubkey}
|
||||
<UserDetail {...params} />
|
||||
{/key}
|
||||
@ -122,7 +122,7 @@
|
||||
>
|
||||
{#if $user}
|
||||
<li>
|
||||
<a href={`/users/${$user.pubkey}`} class="flex gap-2 px-4 py-2 pb-6 items-center">
|
||||
<a href={`/users/${$user.pubkey}/notes`} class="flex gap-2 px-4 py-2 pb-6 items-center">
|
||||
<div
|
||||
class="overflow-hidden w-6 h-6 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({$user.picture})" />
|
||||
|
86
src/partials/Likes.svelte
Normal file
86
src/partials/Likes.svelte
Normal file
@ -0,0 +1,86 @@
|
||||
<script>
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {uniqBy, sortBy, prop, identity} from 'ramda'
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {Cursor, Listener, epoch, findReplyTo, channels} from 'src/state/nostr'
|
||||
import {createScroller, annotateNotes, modal} from "src/state/app"
|
||||
|
||||
export let filter
|
||||
export let notes
|
||||
|
||||
let cursor
|
||||
let listener
|
||||
let scroller
|
||||
let modalUnsub
|
||||
let interval
|
||||
let loading = true
|
||||
|
||||
const addLikes = async likes => {
|
||||
const noteIds = likes.filter(e => e.content === '+').map(findReplyTo).filter(identity)
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const chunk = await channels.getter.all({kinds: [1], ids: noteIds})
|
||||
const annotated = await annotateNotes(chunk, {showParents: true})
|
||||
|
||||
notes.update($notes => sortBy(n => -n.created_at, uniqBy(prop('id'), $notes.concat(annotated))))
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
cursor = new Cursor(filter)
|
||||
listener = new Listener(filter, e => switcherFn(e.kind, {5: () => addLikes([e])}))
|
||||
scroller = createScroller(cursor, addLikes)
|
||||
|
||||
// Track loading based on cursor cutoff date
|
||||
interval = setInterval(() => {
|
||||
loading = cursor.since > epoch
|
||||
}, 1000)
|
||||
|
||||
// When a modal opens, suspend our subscriptions
|
||||
modalUnsub = modal.subscribe(async $modal => {
|
||||
if ($modal) {
|
||||
cursor.stop()
|
||||
listener.stop()
|
||||
scroller.stop()
|
||||
} else {
|
||||
cursor.start()
|
||||
listener.start()
|
||||
scroller.start()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
cursor?.stop()
|
||||
listener?.stop()
|
||||
scroller?.stop()
|
||||
modalUnsub?.()
|
||||
clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={scroller?.start} />
|
||||
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each $notes as n (n.id)}
|
||||
<li>
|
||||
<Note interactive note={n} />
|
||||
{#each n.replies as r (r.id)}
|
||||
<div class="ml-6 border-l border-solid border-medium">
|
||||
<Note interactive isReply note={r} />
|
||||
</div>
|
||||
{/each}
|
||||
</li>
|
||||
{:else}
|
||||
{#if loading}
|
||||
<li><Spinner /></li>
|
||||
{:else}
|
||||
<li class="p-20 text-center" in:fly={{y: 20}}>No notes found.</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {uniqBy, reject, prop} from 'ramda'
|
||||
import {uniqBy, sortBy, reject, prop} from 'ramda'
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {Cursor, epoch} from 'src/state/nostr'
|
||||
@ -29,7 +29,7 @@
|
||||
|
||||
const annotated = await annotateNotes(chunk, {showParents: true})
|
||||
|
||||
notes.update($notes => uniqBy(prop('id'), $notes.concat(annotated)))
|
||||
notes.update($notes => sortBy(n => -n.created_at, uniqBy(prop('id'), $notes.concat(annotated))))
|
||||
})
|
||||
|
||||
// Track loading based on cursor cutoff date
|
||||
|
@ -2,9 +2,9 @@
|
||||
export let user
|
||||
</script>
|
||||
|
||||
<a href={`/users/${user?.pubkey}`} class="flex gap-2 items-center">
|
||||
<a href={`/users/${user.pubkey}/notes`} class="flex gap-2 items-center">
|
||||
<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({user?.picture})" />
|
||||
<span class="text-lg font-bold">{user?.name || user?.pubkey.slice(0, 8)}</span>
|
||||
style="background-image: url({user.picture})" />
|
||||
<span class="text-lg font-bold">{user.name || user.pubkey.slice(0, 8)}</span>
|
||||
</a>
|
||||
|
@ -43,7 +43,7 @@
|
||||
} else {
|
||||
await dispatch("account/update", values)
|
||||
|
||||
navigate(`/users/${$user.pubkey}`)
|
||||
navigate(`/users/${$user.pubkey}/profile`)
|
||||
|
||||
toast.show("info", "Your profile has been updated!")
|
||||
}
|
||||
|
@ -88,7 +88,7 @@
|
||||
{#each (results || []) as e (e.pubkey)}
|
||||
{#if e.pubkey !== $user.pubkey}
|
||||
<li in:fly={{y: 20}}>
|
||||
<a href="/users/{e.pubkey}" class="flex gap-4 my-4">
|
||||
<a href="/users/{e.pubkey}/notes" class="flex gap-4 my-4">
|
||||
<div
|
||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({e.picture})" />
|
||||
|
@ -1,19 +1,27 @@
|
||||
<script>
|
||||
import {writable} from 'svelte/store'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import Notes from "src/partials/Notes.svelte"
|
||||
import Likes from "src/partials/Likes.svelte"
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import {user as currentUser} from 'src/state/user'
|
||||
import {t, dispatch} from 'src/state/dispatch'
|
||||
import {accounts, getFollow, modal} from "src/state/app"
|
||||
|
||||
export let pubkey
|
||||
export let activeTab
|
||||
|
||||
const user = $accounts[pubkey]
|
||||
const notes = writable([])
|
||||
const likes = writable([])
|
||||
const network = writable([])
|
||||
const authors = $currentUser ? $currentUser.petnames.map(t => t[1]) : []
|
||||
|
||||
const authorNotes = writable([])
|
||||
let following = getFollow(pubkey)
|
||||
let user
|
||||
|
||||
$: user = $accounts[pubkey]
|
||||
const setActiveTab = tab => navigate(`/users/${pubkey}/${tab}`)
|
||||
|
||||
const follow = () => {
|
||||
const petnames = $currentUser.petnames
|
||||
@ -38,21 +46,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if user}
|
||||
<div class="max-w-2xl m-auto flex flex-col gap-4 py-8 px-4">
|
||||
<div class="max-w-xl m-auto flex flex-col gap-4 py-8 px-4">
|
||||
<div class="flex flex-col gap-4" in:fly={{y: 20}}>
|
||||
<div class="flex gap-4">
|
||||
<div
|
||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({user.picture})" />
|
||||
style="background-image: url({user?.picture})" />
|
||||
<div class="flex-grow">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-2xl">{user.name}</h1>
|
||||
<h1 class="text-2xl">{user?.name || pubkey.slice(0, 8)}</h1>
|
||||
{#if $currentUser && $currentUser.pubkey !== pubkey}
|
||||
<i class="fa-solid fa-sliders cursor-pointer" on:click={openAdvanced} />
|
||||
{/if}
|
||||
</div>
|
||||
<p>{user.about || ''}</p>
|
||||
<p>{user?.about || ''}</p>
|
||||
</div>
|
||||
<div class="whitespace-nowrap">
|
||||
{#if $currentUser?.pubkey === pubkey}
|
||||
@ -71,7 +78,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-px bg-medium" in:fly={{y: 20, delay: 200}} />
|
||||
<Notes notes={authorNotes} filter={{kinds: [1], authors: [pubkey]}} />
|
||||
</div>
|
||||
<Tabs tabs={['notes', 'likes', 'network']} {activeTab} {setActiveTab} />
|
||||
{#if activeTab === 'notes'}
|
||||
<Notes notes={notes} filter={{kinds: [1], authors: [pubkey]}} />
|
||||
{:else if activeTab === 'likes'}
|
||||
<Likes notes={likes} filter={{kinds: [7], authors: [pubkey]}} />
|
||||
{:else if activeTab === 'network'}
|
||||
<Notes notes={network} filter={{kinds: [1], authors}} shouldMuffle />
|
||||
{/if}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {when, assoc, prop, identity, whereEq, reverse, uniq, sortBy, uniqBy, find, last, pluck, groupBy} from 'ramda'
|
||||
import {when, prop, identity, whereEq, reverse, uniq, sortBy, uniqBy, find, last, pluck, groupBy} from 'ramda'
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {writable, get} from 'svelte/store'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {switcherFn, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {getLocalJson, setLocalJson, now, timedelta, sleep} from "src/util/misc"
|
||||
import {user} from 'src/state/user'
|
||||
import {epoch, filterMatches, Listener, channels, relays, findReplyTo} from 'src/state/nostr'
|
||||
@ -179,38 +179,35 @@ export const notesListener = (notes, filter, {shouldMuffle = false} = {}) => {
|
||||
reactions: n.reactions.filter(e => !ids.includes(e.id)),
|
||||
}))
|
||||
|
||||
return new Listener(
|
||||
ensurePlural(filter).map(assoc('since', now())),
|
||||
e => switcherFn(e.kind, {
|
||||
1: async () => {
|
||||
const id = findReplyTo(e)
|
||||
return new Listener(filter, e => switcherFn(e.kind, {
|
||||
1: async () => {
|
||||
const id = findReplyTo(e)
|
||||
|
||||
if (shouldMuffle && Math.random() > getMuffleValue(e.pubkey)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (id) {
|
||||
const [reply] = await annotateNotes([e])
|
||||
|
||||
updateNote(id, n => ({...n, replies: n.replies.concat(reply)}))
|
||||
} else if (filterMatches(filter, e)) {
|
||||
const [note] = await annotateNotes([e])
|
||||
|
||||
notes.update($notes => uniqBy(prop('id'), [note].concat($notes)))
|
||||
}
|
||||
},
|
||||
5: () => {
|
||||
const ids = e.tags.map(t => t[1])
|
||||
|
||||
notes.update($notes => deleteNotes($notes, ids))
|
||||
},
|
||||
7: () => {
|
||||
const id = findReplyTo(e)
|
||||
|
||||
updateNote(id, n => ({...n, reactions: n.reactions.concat(e)}))
|
||||
if (shouldMuffle && Math.random() > getMuffleValue(e.pubkey)) {
|
||||
return
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (id) {
|
||||
const [reply] = await annotateNotes([e])
|
||||
|
||||
updateNote(id, n => ({...n, replies: n.replies.concat(reply)}))
|
||||
} else if (filterMatches(filter, e)) {
|
||||
const [note] = await annotateNotes([e])
|
||||
|
||||
notes.update($notes => uniqBy(prop('id'), [note].concat($notes)))
|
||||
}
|
||||
},
|
||||
5: () => {
|
||||
const ids = e.tags.map(t => t[1])
|
||||
|
||||
notes.update($notes => deleteNotes($notes, ids))
|
||||
},
|
||||
7: () => {
|
||||
const id = findReplyTo(e)
|
||||
|
||||
updateNote(id, n => ({...n, reactions: n.reactions.concat(e)}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// UI
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {writable, get} from 'svelte/store'
|
||||
import {relayPool, getPublicKey} from 'nostr-tools'
|
||||
import {last, find, intersection, uniqBy, prop} from 'ramda'
|
||||
import {assoc, last, find, intersection, uniqBy, prop} from 'ramda'
|
||||
import {first, noop, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
import {getLocalJson, setLocalJson, now, timedelta} from "src/util/misc"
|
||||
|
||||
@ -184,7 +184,7 @@ export class Cursor {
|
||||
|
||||
export class Listener {
|
||||
constructor(filter, onEvent) {
|
||||
this.filter = ensurePlural(filter)
|
||||
this.filter = ensurePlural(filter).map(assoc('since', now()))
|
||||
this.onEvent = onEvent
|
||||
this.since = now()
|
||||
this.sub = null
|
||||
|
Loading…
Reference in New Issue
Block a user