mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add follow
This commit is contained in:
parent
28c5b5bb71
commit
22e1578869
11
README.md
11
README.md
@ -1,8 +1,10 @@
|
|||||||
Bugs
|
Bugs
|
||||||
|
|
||||||
|
- [ ] Prevent tabs from re-mounting (or at least re- animating)
|
||||||
|
- [ ] Go "back" after adding a note
|
||||||
|
- [ ] When the back button is clicked while a modal is open, close the modal instead
|
||||||
- [ ] uniq is sprinkled all over the place, figure out a better solution for de-duplication
|
- [ ] uniq is sprinkled all over the place, figure out a better solution for de-duplication
|
||||||
- [ ] Fix replies - notes may only include a "root" in its tags
|
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
||||||
- [ ] Reactions in modal are broken
|
|
||||||
|
|
||||||
Features
|
Features
|
||||||
|
|
||||||
@ -11,9 +13,10 @@ Features
|
|||||||
- [x] Search
|
- [x] Search
|
||||||
- [ ] Permalink note detail (share/permalink button?)
|
- [ ] Permalink note detail (share/permalink button?)
|
||||||
- [ ] Add "view thread" page that recurs more deeply
|
- [ ] Add "view thread" page that recurs more deeply
|
||||||
- [ ] Link previews https://github.com/Dhaiwat10/svelte-link-preview, https://microlink.io/sdk
|
- [ ] Fix replies - notes may only include a "root" in its tags
|
||||||
|
- [x] Link previews https://github.com/Dhaiwat10/svelte-link-preview, https://microlink.io/sdk
|
||||||
- Make them opt-in
|
- Make them opt-in
|
||||||
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
- [ ] Add notes, follows, likes tab to profile
|
||||||
- [ ] Followers, blocks, likes on profile
|
- [ ] Followers, blocks, likes on profile
|
||||||
- [ ] Notifications
|
- [ ] Notifications
|
||||||
- [ ] Images
|
- [ ] Images
|
||||||
|
@ -82,7 +82,11 @@
|
|||||||
<Search {...params} />
|
<Search {...params} />
|
||||||
{/key}
|
{/key}
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/notes" component={Notes} />
|
<Route path="/notes/:type" let:params>
|
||||||
|
{#key params.type}
|
||||||
|
<Notes {...params} />
|
||||||
|
{/key}
|
||||||
|
</Route>
|
||||||
<Route path="/notes/new" component={NoteCreate} />
|
<Route path="/notes/new" component={NoteCreate} />
|
||||||
<Route path="/chat" component={Chat} />
|
<Route path="/chat" component={Chat} />
|
||||||
<Route path="/chat/new" component={ChatEdit} />
|
<Route path="/chat/new" component={ChatEdit} />
|
||||||
@ -126,7 +130,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">
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/notes/global">
|
||||||
<i class="fa-solid fa-tag mr-2" /> Notes
|
<i class="fa-solid fa-tag mr-2" /> Notes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
if ($relays.length === 0) {
|
if ($relays.length === 0) {
|
||||||
navigate('/relays')
|
navigate('/relays')
|
||||||
} else if (found) {
|
} else if (found) {
|
||||||
navigate('/notes')
|
navigate('/notes/global')
|
||||||
} else {
|
} else {
|
||||||
navigate('/profile')
|
navigate('/profile')
|
||||||
}
|
}
|
||||||
|
@ -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'))
|
onMount(() => navigate('/notes/global'))
|
||||||
</script>
|
</script>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
toast.show("info", `Your note has been created!`)
|
toast.show("info", `Your note has been created!`)
|
||||||
|
|
||||||
navigate('/notes')
|
navigate('/notes/global')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {onMount, onDestroy} from 'svelte'
|
import {onMount, onDestroy} from 'svelte'
|
||||||
|
import {fly} from 'svelte/transition'
|
||||||
import {writable} from 'svelte/store'
|
import {writable} from 'svelte/store'
|
||||||
import {navigate} from "svelte-routing"
|
import {navigate} from "svelte-routing"
|
||||||
import {uniqBy, prop} from 'ramda'
|
import {uniqBy, prop} from 'ramda'
|
||||||
@ -7,20 +8,24 @@
|
|||||||
import Spinner from "src/partials/Spinner.svelte"
|
import Spinner from "src/partials/Spinner.svelte"
|
||||||
import Note from "src/partials/Note.svelte"
|
import Note from "src/partials/Note.svelte"
|
||||||
import {relays, Cursor} from "src/state/nostr"
|
import {relays, Cursor} from "src/state/nostr"
|
||||||
|
import {user} from "src/state/user"
|
||||||
import {createScroller, annotateNotes, notesListener, modal} from "src/state/app"
|
import {createScroller, annotateNotes, notesListener, modal} from "src/state/app"
|
||||||
|
|
||||||
|
export let type
|
||||||
|
|
||||||
const notes = writable([])
|
const notes = writable([])
|
||||||
let cursor
|
let cursor
|
||||||
let listener
|
let listener
|
||||||
let scroller
|
let scroller
|
||||||
let modalUnsub
|
let modalUnsub
|
||||||
|
let authors = $user ? $user.petnames.map(t => t[1]) : []
|
||||||
|
|
||||||
const createNote = () => {
|
const createNote = () => {
|
||||||
navigate("/notes/new")
|
navigate("/notes/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
cursor = new Cursor({kinds: [1]})
|
cursor = new Cursor(type === 'global' ? {kinds: [1]} : {kinds: [1], authors})
|
||||||
listener = await notesListener(notes, {kinds: [1, 5, 7]})
|
listener = await notesListener(notes, {kinds: [1, 5, 7]})
|
||||||
scroller = createScroller(cursor, async chunk => {
|
scroller = createScroller(cursor, async chunk => {
|
||||||
const annotated = await annotateNotes(chunk, {showParents: true})
|
const annotated = await annotateNotes(chunk, {showParents: true})
|
||||||
@ -52,6 +57,35 @@
|
|||||||
|
|
||||||
<svelte:window on:scroll={scroller?.start} />
|
<svelte:window on:scroll={scroller?.start} />
|
||||||
|
|
||||||
|
{#if $relays.length === 0}
|
||||||
|
<div class="flex w-full justify-center items-center py-16">
|
||||||
|
<div class="text-center max-w-md">
|
||||||
|
You aren't yet connected to any relays. Please click <Anchor href="/relays"
|
||||||
|
>here</Anchor
|
||||||
|
> to get started.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<ul class="border-b border-solid border-dark flex max-w-xl m-auto pt-2" in:fly={{y: 20}}>
|
||||||
|
<li
|
||||||
|
class="cursor-pointer hover:border-b border-solid border-medium"
|
||||||
|
class:border-b={type === 'global'}>
|
||||||
|
<a class="block px-8 py-4 " href="/notes/global">Global</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="cursor-pointer hover:border-b border-solid border-medium"
|
||||||
|
class:border-b={type === 'follows'}>
|
||||||
|
<a class="block px-8 py-4 " href="/notes/follows">Follows</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{#if type === 'follows' && authors.length === 0}
|
||||||
|
<div class="flex w-full justify-center items-center py-16">
|
||||||
|
<div class="text-center max-w-md">
|
||||||
|
You haven't yet followed anyone. Visit a user's profile to follow them.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
|
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
|
||||||
{#each (notes ? $notes : []) as n (n.id)}
|
{#each (notes ? $notes : []) as n (n.id)}
|
||||||
<li class="border-l border-solid border-medium">
|
<li class="border-l border-solid border-medium">
|
||||||
@ -67,8 +101,8 @@
|
|||||||
|
|
||||||
<!-- This will always be sitting at the bottom in case infinite scrolling can't keep up -->
|
<!-- This will always be sitting at the bottom in case infinite scrolling can't keep up -->
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $relays.length > 0}
|
|
||||||
<div class="fixed bottom-0 right-0 p-8">
|
<div class="fixed bottom-0 right-0 p-8">
|
||||||
<div
|
<div
|
||||||
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
|
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
|
||||||
@ -78,13 +112,5 @@
|
|||||||
<span class="fa-sold fa-plus fa-2xl" />
|
<span class="fa-sold fa-plus fa-2xl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
|
||||||
<div class="flex w-full justify-center items-center py-16">
|
|
||||||
<div class="text-center max-w-md">
|
|
||||||
You aren't yet connected to any relays. Please click <Anchor href="/relays"
|
|
||||||
>here</Anchor
|
|
||||||
> to get started.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
settings.set(values)
|
settings.set(values)
|
||||||
|
|
||||||
navigate('/notes')
|
navigate('/notes/global')
|
||||||
|
|
||||||
toast.show("info", "Your settings have been saved!")
|
toast.show("info", "Your settings have been saved!")
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@
|
|||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import Note from "src/partials/Note.svelte"
|
import Note from "src/partials/Note.svelte"
|
||||||
import Spinner from "src/partials/Spinner.svelte"
|
import Spinner from "src/partials/Spinner.svelte"
|
||||||
|
import Button from "src/partials/Button.svelte"
|
||||||
import {Cursor, epoch} from 'src/state/nostr'
|
import {Cursor, epoch} from 'src/state/nostr'
|
||||||
import {user as currentUser} from 'src/state/user'
|
import {user as currentUser} from 'src/state/user'
|
||||||
import {accounts, createScroller, notesListener, modal, annotateNotes} from "src/state/app"
|
import {t, dispatch} from 'src/state/dispatch'
|
||||||
|
import {accounts, getFollow, createScroller, notesListener, modal, annotateNotes} from "src/state/app"
|
||||||
|
|
||||||
export let pubkey
|
export let pubkey
|
||||||
|
|
||||||
@ -19,6 +21,7 @@
|
|||||||
let interval
|
let interval
|
||||||
let loading = true
|
let loading = true
|
||||||
let modalUnsub
|
let modalUnsub
|
||||||
|
let following = getFollow(pubkey)
|
||||||
|
|
||||||
$: user = $accounts[pubkey]
|
$: user = $accounts[pubkey]
|
||||||
|
|
||||||
@ -59,6 +62,25 @@
|
|||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const follow = () => {
|
||||||
|
const petnames = $currentUser.petnames
|
||||||
|
.concat([t("p", user.pubkey, user.name)])
|
||||||
|
|
||||||
|
console.log(petnames)
|
||||||
|
|
||||||
|
dispatch('account/petnames', petnames)
|
||||||
|
|
||||||
|
following = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const unfollow = () => {
|
||||||
|
const petnames = $currentUser.petnames
|
||||||
|
.filter(([_, pubkey]) => pubkey !== user.pubkey)
|
||||||
|
|
||||||
|
dispatch('account/petnames', petnames)
|
||||||
|
|
||||||
|
following = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:scroll={scroller?.start} />
|
<svelte:window on:scroll={scroller?.start} />
|
||||||
@ -73,13 +95,23 @@
|
|||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="text-2xl">{user.name}</h1>
|
<h1 class="text-2xl">{user.name}</h1>
|
||||||
|
</div>
|
||||||
|
<p>{user.about || ''}</p>
|
||||||
|
</div>
|
||||||
|
<div class="whitespace-nowrap">
|
||||||
{#if $currentUser?.pubkey === pubkey}
|
{#if $currentUser?.pubkey === pubkey}
|
||||||
<a href="/settings/profile" class="cursor-pointer text-sm">
|
<a href="/settings/profile" class="cursor-pointer text-sm">
|
||||||
<i class="fa-solid fa-edit" /> Edit
|
<i class="fa-solid fa-edit" /> Edit
|
||||||
</a>
|
</a>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#if following}
|
||||||
|
<Button on:click={unfollow}>Unfollow</Button>
|
||||||
|
{:else}
|
||||||
|
<Button on:click={follow}>Follow</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p>{user.about || ''}</p>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,17 +51,30 @@ export const ensureAccounts = async (pubkeys, {force = false} = {}) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (pubkeys.length) {
|
if (pubkeys.length) {
|
||||||
const events = await channels.getter.all({kinds: [0], authors: uniq(pubkeys)})
|
const events = await channels.getter.all({kinds: [0, 3, 12165], authors: uniq(pubkeys)})
|
||||||
|
|
||||||
await accounts.update($accounts => {
|
await accounts.update($accounts => {
|
||||||
events.forEach(e => {
|
events.forEach(e => {
|
||||||
$accounts[e.pubkey] = {
|
const values = {
|
||||||
pubkey: e.pubkey,
|
muffle: [],
|
||||||
|
petnames: [],
|
||||||
...$accounts[e.pubkey],
|
...$accounts[e.pubkey],
|
||||||
...JSON.parse(e.content),
|
pubkey: e.pubkey,
|
||||||
refreshed: now(),
|
refreshed: now(),
|
||||||
isUser: true,
|
isUser: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switcherFn(e.kind, {
|
||||||
|
0: () => {
|
||||||
|
$accounts[e.pubkey] = {...values, ...JSON.parse(e.content)}
|
||||||
|
},
|
||||||
|
3: () => {
|
||||||
|
$accounts[e.pubkey] = {...values, petnames: e.tags}
|
||||||
|
},
|
||||||
|
12165: () => {
|
||||||
|
$accounts[e.pubkey] = {...values, muffle: e.tags}
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return $accounts
|
return $accounts
|
||||||
@ -72,6 +85,18 @@ export const ensureAccounts = async (pubkeys, {force = false} = {}) => {
|
|||||||
user.update($user => $user ? {...$user, ...get(accounts)[$user.pubkey]} : null)
|
user.update($user => $user ? {...$user, ...get(accounts)[$user.pubkey]} : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getFollow = pubkey => {
|
||||||
|
const $user = get(user)
|
||||||
|
|
||||||
|
return $user && find(t => t[1] === pubkey, $user.petnames)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMuffle = pubkey => {
|
||||||
|
const $user = get(user)
|
||||||
|
|
||||||
|
return $user && find(t => t[1] === pubkey, $user.muffle)
|
||||||
|
}
|
||||||
|
|
||||||
// Notes
|
// Notes
|
||||||
|
|
||||||
export const annotateNotes = async (chunk, {showParents = false} = {}) => {
|
export const annotateNotes = async (chunk, {showParents = false} = {}) => {
|
||||||
|
@ -18,7 +18,13 @@ dispatch.addMethod("account/init", async (topic, privkey) => {
|
|||||||
const pubkey = getPublicKey(privkey)
|
const pubkey = getPublicKey(privkey)
|
||||||
|
|
||||||
// Set what we know about the user to our store
|
// Set what we know about the user to our store
|
||||||
user.set({name: pubkey.slice(0, 8), privkey, pubkey})
|
user.set({
|
||||||
|
name: pubkey.slice(0, 8),
|
||||||
|
privkey,
|
||||||
|
pubkey,
|
||||||
|
petnames: [],
|
||||||
|
muffle: [],
|
||||||
|
})
|
||||||
|
|
||||||
// Make sure we have data for this user
|
// Make sure we have data for this user
|
||||||
await ensureAccounts([pubkey], {force: true})
|
await ensureAccounts([pubkey], {force: true})
|
||||||
@ -35,6 +41,26 @@ dispatch.addMethod("account/update", async (topic, updates) => {
|
|||||||
await nostr.publish(nostr.event(0, JSON.stringify(updates)))
|
await nostr.publish(nostr.event(0, JSON.stringify(updates)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
dispatch.addMethod("account/petnames", async (topic, petnames) => {
|
||||||
|
const $user = get(user)
|
||||||
|
|
||||||
|
// Update our local copy
|
||||||
|
user.set({...$user, petnames})
|
||||||
|
|
||||||
|
// Tell the network
|
||||||
|
await nostr.publish(nostr.event(3, '', petnames))
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch.addMethod("account/muffle", async (topic, muffle) => {
|
||||||
|
const $user = get(user)
|
||||||
|
|
||||||
|
// Update our local copy
|
||||||
|
user.set({...$user, muffle})
|
||||||
|
|
||||||
|
// Tell the network
|
||||||
|
await nostr.publish(nostr.event(12165, '', muffle))
|
||||||
|
})
|
||||||
|
|
||||||
dispatch.addMethod("relay/join", async (topic, url) => {
|
dispatch.addMethod("relay/join", async (topic, url) => {
|
||||||
const $user = get(user)
|
const $user = get(user)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user