Fix a few bugs

This commit is contained in:
Jonathan Staab 2023-04-18 09:32:07 -05:00
parent eaa517d71a
commit dfb1916b02
11 changed files with 85 additions and 63 deletions

2
.env
View File

@ -1,3 +1,3 @@
VITE_THEME_DARK=transparent:transparent,black:#0f0f0e,white:#FFFFFF,accent:#EB5E28,accent-light:#FB652C,gray-1:#FFFFFF,gray-2:#FAF6F1,gray-3:#F2EBE1,gray-4:#E9E0D3,gray-5:#B3AA98,gray-6:#565249,gray-7:#393530,gray-8:#252422,danger:#ff0000,warning:#ebd112,success:#37ab51,input:#FAF6F1,input-hover:#F2EBE1 VITE_THEME_DARK=transparent:transparent,black:#0f0f0e,white:#FFFFFF,accent:#EB5E28,accent-light:#FB652C,gray-1:#FFFFFF,gray-2:#FAF6F1,gray-3:#F2EBE1,gray-4:#E9E0D3,gray-5:#B3AA98,gray-6:#565249,gray-7:#393530,gray-8:#252422,danger:#ff0000,warning:#ebd112,success:#37ab51,input:#FAF6F1,input-hover:#F2EBE1
VITE_THEME_LIGHT=transparent:transparent,black:#0f0f0e,white:#FFFFFF,accent:#EB5E28,accent-light:#FB652C,gray-8:#FFFFFF,gray-7:#FAF6F1,gray-6:#F2EBE1,gray-5:#E9E0D3,gray-4:#B3AA98,gray-3:#565249,gray-2:#393530,gray-1:#252422,danger:#ff0000,warning:#ebd112,success:#37ab51,input:#FAF6F1,input-hover:#F2EBE1 VITE_THEME_LIGHT=transparent:transparent,black:#0f0f0e,white:#FFFFFF,accent:#EB5E28,accent-light:#FB652C,gray-8:#FFFFFF,gray-7:#FAF6F1,gray-6:#F2EBE1,gray-5:#B3AA98,gray-4:#B3AA98,gray-3:#565249,gray-2:#393530,gray-1:#252422,danger:#ff0000,warning:#ebd112,success:#37ab51,input:#FAF6F1,input-hover:#F2EBE1
VITE_DUFFLEPUD_URL=https://dufflepud.onrender.com VITE_DUFFLEPUD_URL=https://dufflepud.onrender.com

View File

@ -1,10 +1,11 @@
# Current # Current
- [ ] Claim relays bounty
- [ ] Add real search, it's a big hurdle for first-timers/anons - [ ] Add real search, it's a big hurdle for first-timers/anons
- [ ] Remember message/chat status - [ ] Remember message/chat status
- [ ] Fix note nesting
- [ ] Image classification - [ ] Image classification
- https://github.com/bhky/opennsfw2 - https://github.com/bhky/opennsfw2
- [ ] Claim relays bounty
# Core # Core
@ -71,6 +72,7 @@
- [ ] Share button for notes, shows qr code and nevent - [ ] Share button for notes, shows qr code and nevent
- [ ] open web+nostr links like snort - [ ] open web+nostr links like snort
- [ ] Pinned posts ala snort - [ ] Pinned posts ala snort
- [ ] Scroll to top button that appears after scrolling a bit
- [ ] Likes list on note detail. Maybe a sidebar or header for note detail page? - [ ] Likes list on note detail. Maybe a sidebar or header for note detail page?
- [ ] Add keyword mutes - [ ] Add keyword mutes
- [ ] Show options on note detail for retrieving replies - [ ] Show options on note detail for retrieving replies

View File

@ -2,6 +2,7 @@
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {find, last} from "ramda" import {find, last} from "ramda"
import {onMount} from "svelte" import {onMount} from "svelte"
import {get} from "svelte/store"
import {quantify} from "hurdak/lib/hurdak" import {quantify} from "hurdak/lib/hurdak"
import {findRootId, findReplyId, displayPerson} from "src/util/nostr" import {findRootId, findReplyId, displayPerson} from "src/util/nostr"
import {formatTimestamp} from "src/util/misc" import {formatTimestamp} from "src/util/misc"
@ -55,24 +56,34 @@
return showContext ? true : !r.isContext return showContext ? true : !r.isContext
}) })
// If we're already in a note detail modal, don't infinitely nest. But if we're in
// some other modal, make it possible to go back.
const goToNote = data => {
if (modal.getCurrent()?.type === "note/detail") {
modal.replace({type: "note/detail", ...data})
} else {
modal.push({type: "note/detail", ...data})
}
}
const onClick = e => { const onClick = e => {
const target = e.target as HTMLElement const target = e.target as HTMLElement
if (interactive && !["I"].includes(target.tagName) && !target.closest("a")) { if (interactive && !["I"].includes(target.tagName) && !target.closest("a")) {
modal.push({type: "note/detail", note}) goToNote({note})
} }
} }
const goToParent = async () => { const goToParent = async () => {
const relays = getRelaysForEventParent(note) const relays = getRelaysForEventParent(note)
modal.push({type: "note/detail", note: {id: findReplyId(note)}, relays}) goToNote({note: {id: findReplyId(note)}, relays})
} }
const goToRoot = async () => { const goToRoot = async () => {
const relays = getRelaysForEventParent(note) const relays = getRelaysForEventParent(note)
modal.push({type: "note/detail", note: {id: findRootId(note)}, relays}) goToNote({note: {id: findRootId(note)}, relays})
} }
const setBorderHeight = () => { const setBorderHeight = () => {

View File

@ -176,19 +176,19 @@
} }
</script> </script>
<div class="flex justify-between text-gray-1"> <div class="flex justify-between text-gray-1" on:click|stopPropagation>
<div <div
class={cx("flex", { class={cx("flex", {
"pointer-events-none opacity-75": !$canPublish || muted, "pointer-events-none opacity-75": !$canPublish || muted,
})}> })}>
<button class="w-16 text-left" on:click|stopPropagation={reply.start}> <button class="w-16 text-left" on:click={reply.start}>
<i class="fa fa-reply cursor-pointer" /> <i class="fa fa-reply cursor-pointer" />
{$repliesCount} {$repliesCount}
</button> </button>
<button <button
class="w-16 text-left" class="w-16 text-left"
class:text-accent={like} class:text-accent={like}
on:click|stopPropagation={() => (like ? deleteReaction(like) : react("+"))}> on:click={() => (like ? deleteReaction(like) : react("+"))}>
<i <i
class={cx("fa fa-heart cursor-pointer", { class={cx("fa fa-heart cursor-pointer", {
"fa-beat fa-beat-custom": like, "fa-beat fa-beat-custom": like,
@ -200,12 +200,12 @@
"pointer-events-none opacity-50": !canZap, "pointer-events-none opacity-50": !canZap,
})} })}
class:text-accent={zap} class:text-accent={zap}
on:click|stopPropagation={startZap}> on:click={startZap}>
<i class="fa fa-bolt cursor-pointer" /> <i class="fa fa-bolt cursor-pointer" />
{formatSats($zapsTotal)} {formatSats($zapsTotal)}
</button> </button>
</div> </div>
<div on:click|stopPropagation class="flex items-center"> <div class="flex items-center">
{#if pool.forceUrls.length === 0} {#if pool.forceUrls.length === 0}
<!-- Mobile version --> <!-- Mobile version -->
<div <div

View File

@ -6,7 +6,7 @@
import {modal} from "src/partials/state" import {modal} from "src/partials/state"
import Popover from "src/partials/Popover.svelte" import Popover from "src/partials/Popover.svelte"
import OverflowMenu from "src/partials/OverflowMenu.svelte" import OverflowMenu from "src/partials/OverflowMenu.svelte"
import {getPubkeyWriteRelays} from "src/agent/relays" import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
import user from "src/agent/user" import user from "src/agent/user"
import pool from "src/agent/pool" import pool from "src/agent/pool"
import {addToList} from "src/app/state" import {addToList} from "src/app/state"
@ -61,7 +61,7 @@
} }
const follow = async () => { const follow = async () => {
const [{url}] = getPubkeyWriteRelays(person.pubkey) const [{url}] = sampleRelays(getPubkeyWriteRelays(person.pubkey))
user.addPetname(person.pubkey, url, displayPerson(person)) user.addPetname(person.pubkey, url, displayPerson(person))
} }

View File

@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
import {last, nth} from "ramda" import {last, nth} from "ramda"
import {displayPerson} from "src/util/nostr" import {displayPerson} from "src/util/nostr"
import Anchor from "src/partials/Anchor.svelte"
import user from "src/agent/user" import user from "src/agent/user"
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays" import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
import {getPersonWithFallback} from "src/agent/db" import {getPersonWithFallback} from "src/agent/db"
import {watch} from "src/agent/db" import {watch} from "src/agent/db"
import PersonCircle from "src/app/shared/PersonCircle.svelte" import PersonCircle from "src/app/shared/PersonCircle.svelte"
import PersonAbout from "src/app/shared/PersonAbout.svelte" import PersonAbout from "src/app/shared/PersonAbout.svelte"
import {routes} from "src/app/state"
export let pubkey export let pubkey
@ -32,16 +34,18 @@
<div class="relative flex flex-col gap-4 py-2 px-3"> <div class="relative flex flex-col gap-4 py-2 px-3">
<div class="flex gap-4"> <div class="flex gap-4">
<PersonCircle size={14} person={$person} /> <Anchor type="unstyled" href={routes.person($person.pubkey)} class="flex gap-4">
<div class="flex flex-grow flex-col gap-2"> <PersonCircle size={14} person={$person} />
<h2 class="text-lg">{displayPerson($person)}</h2> <div class="flex flex-grow flex-col gap-2">
{#if $person.verified_as} <h2 class="text-lg">{displayPerson($person)}</h2>
<div class="flex gap-1 text-sm"> {#if $person.verified_as}
<i class="fa fa-user-check text-accent" /> <div class="flex gap-1 text-sm">
<span class="text-gray-1">{last($person.verified_as.split("@"))}</span> <i class="fa fa-user-check text-accent" />
</div> <span class="opacity-75">{last($person.verified_as.split("@"))}</span>
{/if} </div>
</div> {/if}
</div>
</Anchor>
<div class="flex gap-4 py-2 text-lg"> <div class="flex gap-4 py-2 text-lg">
{#if $canPublish} {#if $canPublish}
{#if muted} {#if muted}

View File

@ -90,6 +90,10 @@ export const logUsage = async name => {
} }
} }
// Feed
export const feedsTab = writable("Follows")
// State // State
export const lastChecked = synced("app/alerts/lastChecked", {}) export const lastChecked = synced("app/alerts/lastChecked", {})
@ -150,7 +154,7 @@ const processChats = async (pubkey, events) => {
lastChecked.update($lastChecked => { lastChecked.update($lastChecked => {
for (const message of messages) { for (const message of messages) {
const id = Tags.from(message).type("e").values().first() const id = Tags.from(message).getMeta("e")
if (message.pubkey === pubkey) { if (message.pubkey === pubkey) {
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at) $lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {prop, uniq, indexBy, objOf, filter as _filter} from "ramda" import {prop, indexBy, objOf, filter as _filter} from "ramda"
import {shuffle, synced} from "src/util/misc" import {shuffle} from "src/util/misc"
import {Tags} from "src/util/nostr" import {Tags} from "src/util/nostr"
import {modal, theme} from "src/partials/state" import {modal, theme} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
@ -12,38 +12,31 @@
import {getUserFollows, getUserNetwork} from "src/agent/social" import {getUserFollows, getUserNetwork} from "src/agent/social"
import {sampleRelays, getAllPubkeyWriteRelays, getUserReadRelays} from "src/agent/relays" import {sampleRelays, getAllPubkeyWriteRelays, getUserReadRelays} from "src/agent/relays"
import user from "src/agent/user" import user from "src/agent/user"
import {feedsTab} from "src/app/state"
const {lists, canPublish} = user const {lists, canPublish} = user
const activeTab = synced("views/Feeds/activeTab", "Follows") const defaultTabs = ["Follows", "Network"]
let relays, filter, tabs let relays, filter
$: listsByName = indexBy(l => Tags.from(l).getMeta("d"), $lists) $: listsByName = indexBy(l => Tags.from(l).getMeta("d"), $lists)
$: { $: allTabs = defaultTabs.concat(Object.keys(listsByName))
const defaultTabs = ["Follows", "Network"] $: $feedsTab = allTabs.includes($feedsTab) ? $feedsTab : defaultTabs[0]
const customTabs = Object.keys(listsByName) $: visibleTabs = defaultTabs.includes($feedsTab) ? defaultTabs : [defaultTabs[0], $feedsTab]
const validTabs = defaultTabs.concat(customTabs)
if (!validTabs.includes($activeTab)) {
$activeTab = validTabs[0]
}
tabs = uniq(defaultTabs.concat($activeTab).concat(customTabs)).slice(0, 3)
}
$: { $: {
if ($activeTab === "Follows") { if ($feedsTab === "Follows") {
const authors = shuffle(getUserFollows()).slice(0, 256) const authors = shuffle(getUserFollows()).slice(0, 256)
filter = {authors} filter = {authors}
relays = sampleRelays(getAllPubkeyWriteRelays(authors)) relays = sampleRelays(getAllPubkeyWriteRelays(authors))
} else if ($activeTab === "Network") { } else if ($feedsTab === "Network") {
const authors = shuffle(getUserNetwork()).slice(0, 256) const authors = shuffle(getUserNetwork()).slice(0, 256)
filter = {authors} filter = {authors}
relays = sampleRelays(getAllPubkeyWriteRelays(authors)) relays = sampleRelays(getAllPubkeyWriteRelays(authors))
} else { } else {
const list = listsByName[$activeTab] const list = listsByName[$feedsTab]
const tags = Tags.from(list) const tags = Tags.from(list)
const authors = tags.type("p").values().all() const authors = tags.type("p").values().all()
const topics = tags.type("t").values().all() const topics = tags.type("t").values().all()
@ -59,14 +52,14 @@
} }
const setActiveTab = tab => { const setActiveTab = tab => {
$activeTab = tab $feedsTab = tab
} }
const showLists = () => { const showLists = () => {
modal.push({type: "list/list"}) modal.push({type: "list/list"})
} }
document.title = $activeTab document.title = $feedsTab
</script> </script>
<Content> <Content>
@ -79,7 +72,7 @@
</Content> </Content>
{/if} {/if}
<div> <div>
<Tabs {tabs} activeTab={$activeTab} {setActiveTab}> <Tabs tabs={visibleTabs} activeTab={$feedsTab} {setActiveTab}>
{#if $canPublish} {#if $canPublish}
{#if $lists.length > 1} {#if $lists.length > 1}
<Popover placement="bottom" opts={{hideOnClick: true}} theme="transparent"> <Popover placement="bottom" opts={{hideOnClick: true}} theme="transparent">
@ -89,19 +82,17 @@
class="flex flex-col items-start overflow-hidden rounded border border-solid border-gray-8 bg-black"> class="flex flex-col items-start overflow-hidden rounded border border-solid border-gray-8 bg-black">
{#each $lists as e (e.id)} {#each $lists as e (e.id)}
{@const meta = Tags.from(e).asMeta()} {@const meta = Tags.from(e).asMeta()}
{#if meta.d !== $activeTab} <button
<button class={cx("w-full py-2 px-3 text-left transition-colors", {
class={cx("w-full py-2 px-3 text-left transition-colors", { "hover:bg-gray-7": $theme === "dark",
"hover:bg-gray-7": $theme === "dark", "hover:bg-gray-1": $theme === "light",
"hover:bg-gray-1": $theme === "light", })}
})} on:click={() => {
on:click={() => { $feedsTab = meta.d
$activeTab = meta.d }}>
}}> <i class="fa fa-scroll fa-sm mr-1" />
<i class="fa fa-scroll fa-sm mr-1" /> {meta.d}
{meta.d} </button>
</button>
{/if}
{/each} {/each}
<button <button
on:click={showLists} on:click={showLists}
@ -118,7 +109,7 @@
{/if} {/if}
{/if} {/if}
</Tabs> </Tabs>
{#key $activeTab} {#key $feedsTab}
<Feed {relays} {filter} /> <Feed {relays} {filter} />
{/key} {/key}
</div> </div>

View File

@ -16,6 +16,7 @@
const topics = t const topics = t
.all() .all()
.map(topic => ({type: "topic", id: topic.name, topic, text: "#" + topic.name})) .map(topic => ({type: "topic", id: topic.name, topic, text: "#" + topic.name}))
const people = p const people = p
.all({"kind0.name": {$type: "string"}, pubkey: {$ne: user.getPubkey()}}) .all({"kind0.name": {$type: "string"}, pubkey: {$ne: user.getPubkey()}})
.map(person => ({ .map(person => ({

View File

@ -24,7 +24,9 @@
}} /> }} />
<div class="modal fixed inset-0 z-30" bind:this={root} transition:fade> <div class="modal fixed inset-0 z-30" bind:this={root} transition:fade>
<div class="fixed inset-0 cursor-pointer bg-black opacity-50" on:click={onEscape} /> <div
class="fixed inset-0 cursor-pointer bg-black opacity-50"
on:click|stopPropagation={onEscape} />
<div <div
class="modal-content h-full overflow-auto" class="modal-content h-full overflow-auto"
bind:this={content} bind:this={content}

View File

@ -48,10 +48,18 @@ export const openModals = writable(0)
export const modal = { export const modal = {
stack: new WritableList([]) as WritableList<any>, stack: new WritableList([]) as WritableList<any>,
sync: ($stack, opts = {}) => { getCurrent() {
const $stack = get(modal.stack)
const $openModals = get(openModals)
return $stack[$openModals - 1]
},
sync($stack, opts = {}) {
const hash = $stack.length > 0 ? `#m=${$stack.length}` : "" const hash = $stack.length > 0 ? `#m=${$stack.length}` : ""
navigate(window.location.pathname + hash, opts) if (hash !== window.location.hash) {
navigate(window.location.pathname + hash, opts)
}
return $stack return $stack
}, },
@ -63,8 +71,7 @@ export const modal = {
await sleep(100) await sleep(100)
}, },
async replace(data) { async replace(data) {
await modal.pop() modal.stack.update($stack => $stack.slice(0, -1).concat(data))
modal.push(data)
}, },
async clear() { async clear() {
const stackSize = (get(modal.stack) as any).length const stackSize = (get(modal.stack) as any).length