Generalize notes/tabs

This commit is contained in:
Jonathan Staab 2022-12-07 22:27:07 -08:00
parent 16884eeb99
commit 70ca44226c
7 changed files with 126 additions and 169 deletions

View File

@ -1,8 +1,6 @@
Bugs
- [ ] Pubkeys expand the width of the page, hiding the plus post button
- [ ] Permalink note detail (share/permalink button?)
- [ ] Back button no longer works if a modal is closed normally
- [ ] 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

View File

@ -92,11 +92,7 @@
<Search {...params} />
{/key}
</Route>
<Route path="/notes/:type" let:params>
{#key params.type}
<Notes {...params} />
{/key}
</Route>
<Route path="/notes/:activeTab" component={Notes} />
<Route path="/notes/new" component={NoteCreate} />
<Route path="/chat" component={Chat} />
<Route path="/chat/new" component={ChatEdit} />

View File

@ -121,7 +121,7 @@
<Anchor on:click={() => deleteReaction(flag)}>Unflag</Anchor>
</p>
{:else}
<p>
<p class="text-ellipsis overflow-hidden">
{#if note.content.length > 500 && !showEntire}
{ellipsize(note.content, 500)}
{:else}

82
src/partials/Notes.svelte Normal file
View File

@ -0,0 +1,82 @@
<script>
import {onMount, onDestroy} from 'svelte'
import {fly} from 'svelte/transition'
import {uniqBy, 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'
import {createScroller, getMuffleValue, annotateNotes, notesListener, modal} from "src/state/app"
export let filter
export let notes
export let shouldMuffle = false
let cursor
let listener
let scroller
let modalUnsub
let interval
let loading = true
onMount(async () => {
cursor = new Cursor(filter)
listener = await notesListener(notes, [filter, {kinds: [5, 7]}], {shouldMuffle})
scroller = createScroller(cursor, async chunk => {
// Remove a sampling of content if desired
if (shouldMuffle) {
chunk = reject(n => Math.random() > getMuffleValue(n.pubkey), chunk)
}
const annotated = await annotateNotes(chunk, {showParents: true})
notes.update($notes => uniqBy(prop('id'), $notes.concat(annotated)))
})
// 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>

19
src/partials/Tabs.svelte Normal file
View File

@ -0,0 +1,19 @@
<script>
import {fly} from 'svelte/transition'
import {toTitle} from 'hurdak/lib/hurdak'
export let tabs
export let activeTab
export let setActiveTab
</script>
<ul class="border-b border-solid border-dark flex max-w-xl m-auto pt-2" in:fly={{y: 20}}>
{#each tabs as tab}
<li
class="cursor-pointer hover:border-b border-solid border-medium px-8 py-4"
class:border-b={activeTab === tab}
on:click={() => setActiveTab(tab)}>
{toTitle(tab)}
</li>
{/each}
</ul>

View File

@ -1,65 +1,21 @@
<script>
import {onMount, onDestroy} from 'svelte'
import {fly} from 'svelte/transition'
import {writable} from 'svelte/store'
import {navigate} from "svelte-routing"
import {uniqBy, reject, prop} from 'ramda'
import {navigate} from 'svelte-routing'
import Anchor from "src/partials/Anchor.svelte"
import Spinner from "src/partials/Spinner.svelte"
import Note from "src/partials/Note.svelte"
import {relays, Cursor} from "src/state/nostr"
import Tabs from "src/partials/Tabs.svelte"
import Notes from "src/partials/Notes.svelte"
import {relays} from "src/state/nostr"
import {user} from "src/state/user"
import {createScroller, getMuffleValue, annotateNotes, notesListener, modal} from "src/state/app"
export let type
export let activeTab
const notes = writable([])
let cursor
let listener
let scroller
let modalUnsub
let authors = $user ? $user.petnames.map(t => t[1]) : []
const globalNotes = writable([])
const followNotes = writable([])
const authors = $user ? $user.petnames.map(t => t[1]) : []
const createNote = () => {
navigate("/notes/new")
}
onMount(async () => {
cursor = new Cursor(type === 'global' ? {kinds: [1]} : {kinds: [1], authors})
listener = await notesListener(notes, {kinds: [1, 5, 7]}, {shouldMuffle: true})
scroller = createScroller(cursor, async chunk => {
// Remove a sampling of content if desired
chunk = reject(n => Math.random() > getMuffleValue(n.pubkey), chunk)
const annotated = await annotateNotes(chunk, {showParents: true})
notes.update($notes => uniqBy(prop('id'), $notes.concat(annotated)))
})
// 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?.()
})
const setActiveTab = tab => navigate(`/notes/${tab}`)
</script>
<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">
@ -69,51 +25,25 @@
</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}
<Tabs tabs={['global', 'follows']} {activeTab} {setActiveTab} />
{#if activeTab === '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 if activeTab === 'follows'}
<Notes notes={followNotes} filter={{kinds: [1], authors}} shouldMuffle />
{:else}
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
{#each (notes ? $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>
{/each}
</ul>
<!-- This will always be sitting at the bottom in case infinite scrolling can't keep up -->
<Spinner />
<Notes notes={globalNotes} filter={{kinds: [1]}} shouldMuffle />
{/if}
<div class="fixed bottom-0 right-0 p-8">
<div
<a
href="/notes/new"
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
items-center border border-dark shadow-2xl cursor-pointer"
on:click={createNote}
>
items-center border border-dark shadow-2xl cursor-pointer">
<span class="fa-sold fa-plus fa-2xl" />
</div>
</a>
</div>
{/if}

View File

@ -1,73 +1,24 @@
<script>
import {onMount, onDestroy} from 'svelte'
import {writable} from 'svelte/store'
import {uniqBy, prop} from 'ramda'
import {fly} from 'svelte/transition'
import Note from "src/partials/Note.svelte"
import Spinner from "src/partials/Spinner.svelte"
import Notes from "src/partials/Notes.svelte"
import Button from "src/partials/Button.svelte"
import {Cursor, epoch} from 'src/state/nostr'
import {user as currentUser} from 'src/state/user'
import {t, dispatch} from 'src/state/dispatch'
import {accounts, getFollow, createScroller, notesListener, modal, annotateNotes} from "src/state/app"
import {accounts, getFollow, modal} from "src/state/app"
export let pubkey
const notes = writable([])
let user
let cursor
let listener
let scroller
let interval
let loading = true
let modalUnsub
const authorNotes = writable([])
let following = getFollow(pubkey)
let user
$: user = $accounts[pubkey]
onMount(async () => {
cursor = new Cursor({kinds: [1], authors: [pubkey]})
listener = await notesListener(notes, [{kinds: [1], authors: [pubkey]}, {kinds: [5, 7]}])
scroller = createScroller(cursor, async chunk => {
const annotated = await annotateNotes(chunk, {showParents: true})
notes.update($notes => uniqBy(prop('id'), $notes.concat(annotated)))
})
// Populate our initial empty space
scroller.start()
// 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()
} else {
cursor.start()
listener.start()
}
})
})
onDestroy(() => {
cursor?.stop()
listener?.stop()
scroller?.stop()
modalUnsub?.()
clearInterval(interval)
})
const follow = () => {
const petnames = $currentUser.petnames
.concat([t("p", user.pubkey, user.name)])
console.log(petnames)
dispatch('account/petnames', petnames)
following = true
@ -87,8 +38,6 @@
}
</script>
<svelte:window on:scroll={scroller?.start} />
{#if user}
<div class="max-w-2xl m-auto flex flex-col gap-4 py-8 px-4">
<div class="flex flex-col gap-4" in:fly={{y: 20}}>
@ -123,23 +72,6 @@
</div>
</div>
<div class="h-px bg-medium" in:fly={{y: 20, delay: 200}} />
<ul class="flex flex-col" in:fly={{y: 20, delay: 400}}>
{#each (notes ? $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>
<Notes notes={authorNotes} filter={{kinds: [1], authors: [pubkey]}} />
</div>
{/if}