Add render support for feeds and new hud

This commit is contained in:
Jon Staab 2024-05-15 15:04:33 -07:00
parent 55db5f174f
commit d74790a4da
22 changed files with 199 additions and 122 deletions

View File

@ -1,7 +1,9 @@
<script lang="ts"> <script lang="ts">
import {equals} from "ramda" import {equals} from "ramda"
import {randomId} from "@welshman/lib" import {seconds} from "hurdak"
import {randomId, now} from "@welshman/lib"
import {makeScopeFeed, Scope} from "@welshman/feeds" import {makeScopeFeed, Scope} from "@welshman/feeds"
import {PublishStatus} from "@welshman/net"
import {fly} from "src/util/transition" import {fly} from "src/util/transition"
import {toggleTheme, theme} from "src/partials/state" import {toggleTheme, theme} from "src/partials/state"
import MenuItem from "src/partials/MenuItem.svelte" import MenuItem from "src/partials/MenuItem.svelte"
@ -26,12 +28,37 @@
displayPubkey, displayPubkey,
userListFeeds, userListFeeds,
userFeeds, userFeeds,
publishes,
} from "src/engine" } from "src/engine"
const {page} = router const {page} = router
const followsFeed = makeFeed({definition: normalizeFeedDefinition(makeScopeFeed(Scope.Follows))}) const followsFeed = makeFeed({definition: normalizeFeedDefinition(makeScopeFeed(Scope.Follows))})
const networkFeed = makeFeed({definition: normalizeFeedDefinition(makeScopeFeed(Scope.Network))}) const networkFeed = makeFeed({definition: normalizeFeedDefinition(makeScopeFeed(Scope.Network))})
const hud = publishes.derived($publishes => {
const pending = []
const success = []
const failure = []
for (const {created_at, request, status} of $publishes) {
if (created_at < now() - seconds(5, "minute")) {
continue
}
const statuses = Array.from(status.values())
if (statuses.includes(PublishStatus.Success)) {
success.push(request.event)
} else if (statuses.includes(PublishStatus.Pending)) {
pending.push(request.event)
} else {
failure.push(request.event)
}
}
return {pending, success, failure}
})
const closeSubMenu = () => { const closeSubMenu = () => {
subMenu = null subMenu = null
} }
@ -228,6 +255,26 @@
</MenuItem> </MenuItem>
</MenuDesktopSecondary> </MenuDesktopSecondary>
{/if} {/if}
<div>
<Anchor
modal
href="/publishes"
class="flex h-12 cursor-pointer items-center justify-between border-t border-solid border-neutral-600 pl-7 pr-12">
<div class="flex items-center gap-1" class:text-tinted-500={$hud.pending.length === 0}>
<i class="fa fa-hourglass" />
{$hud.pending.length}
</div>
<div class="flex items-center gap-1" class:text-tinted-500={$hud.success.length === 0}>
<i class="fa fa-cloud-arrow-up" />
{$hud.success.length}
</div>
<div class="flex items-center gap-1"
class:text-accent={$hud.failure.length > 0}
class:text-tinted-500={$hud.failure.length === 0}>
<i class="fa fa-triangle-exclamation" />
{$hud.failure.length}
</div>
</Anchor>
<div class="h-20 cursor-pointer border-t border-solid border-neutral-600 px-7 py-4"> <div class="h-20 cursor-pointer border-t border-solid border-neutral-600 px-7 py-4">
{#if $pubkey} {#if $pubkey}
<Anchor class="flex items-center gap-2" on:click={() => setSubMenu("account")}> <Anchor class="flex items-center gap-2" on:click={() => setSubMenu("account")}>
@ -241,5 +288,6 @@
<Anchor modal button accent href="/login">Log In</Anchor> <Anchor modal button accent href="/login">Log In</Anchor>
{/if} {/if}
</div> </div>
</div>
</FlexColumn> </FlexColumn>
</div> </div>

View File

@ -122,7 +122,7 @@
<i class="fa fa-rss" /> Feeds <i class="fa fa-rss" /> Feeds
</MenuMobileItem> </MenuMobileItem>
</div> </div>
<div class="staatliches mt-8 block flex h-8 justify-center gap-2 px-8 text-neutral-300"> <div class="staatliches mt-8 block flex h-8 justify-center gap-2 px-8 text-tinted-400">
<Anchor class="hover:text-tinted-200" href="/about">About</Anchor> / <Anchor class="hover:text-tinted-200" href="/about">About</Anchor> /
<Anchor external class="hover:text-tinted-200" href="/terms.html">Terms</Anchor> / <Anchor external class="hover:text-tinted-200" href="/terms.html">Terms</Anchor> /
<Anchor external class="hover:text-tinted-200" href="/privacy.html">Privacy</Anchor> <Anchor external class="hover:text-tinted-200" href="/privacy.html">Privacy</Anchor>
@ -192,7 +192,7 @@
<i class="fa fa-paper-plane" /> Create Invite <i class="fa fa-paper-plane" /> Create Invite
</MenuMobileItem> </MenuMobileItem>
</div> </div>
<div class="staatliches block flex h-8 justify-center gap-2 px-8 text-neutral-300"> <div class="staatliches block flex h-8 justify-center gap-2 px-8 text-tinted-400">
<Anchor class="hover:text-tinted-200" href="/logout" on:click={closeMenu}>Logout</Anchor> / <Anchor class="hover:text-tinted-200" href="/logout" on:click={closeMenu}>Logout</Anchor> /
<Anchor class="hover:text-tinted-200" stopPropagation on:click={() => setSubMenu("accounts")}> <Anchor class="hover:text-tinted-200" stopPropagation on:click={() => setSubMenu("accounts")}>
Switch Accounts Switch Accounts

View File

@ -1,7 +1,4 @@
<script lang="ts"> <script lang="ts">
import {now} from "@welshman/lib"
import {PublishStatus} from "@welshman/net"
import {quantify, seconds} from "hurdak"
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import SearchResults from "src/app/shared/SearchResults.svelte" import SearchResults from "src/app/shared/SearchResults.svelte"
@ -9,37 +6,13 @@
import PersonBadge from "src/app/shared/PersonBadge.svelte" import PersonBadge from "src/app/shared/PersonBadge.svelte"
import {menuIsOpen, searchTerm} from "src/app/state" import {menuIsOpen, searchTerm} from "src/app/state"
import {router} from "src/app/util/router" import {router} from "src/app/util/router"
import {env, pubkey, canSign, hasNewNotifications, hasNewMessages, publishes} from "src/engine" import {env, pubkey, canSign, hasNewNotifications, hasNewMessages} from "src/engine"
let innerWidth = 0 let innerWidth = 0
let searchInput let searchInput
const {page} = router const {page} = router
const hud = publishes.derived($publishes => {
const pending = []
const success = []
const failure = []
for (const {created_at, request, status} of $publishes) {
if (created_at < now() - seconds(5, "minute")) {
continue
}
const statuses = Array.from(status.values())
if (statuses.includes(PublishStatus.Pending)) {
pending.push(request.event)
} else if (statuses.includes(PublishStatus.Success)) {
success.push(request.event)
} else {
failure.push(request.event)
}
}
return {pending, success, failure}
})
const openMenu = () => menuIsOpen.set(true) const openMenu = () => menuIsOpen.set(true)
const openSearch = () => router.at("/search").open() const openSearch = () => router.at("/search").open()
@ -78,22 +51,6 @@
{#if innerWidth >= 1024} {#if innerWidth >= 1024}
<div <div
class="fixed left-0 right-0 top-0 z-nav flex h-16 items-center justify-end gap-8 bg-neutral-900 pl-4 pr-8"> class="fixed left-0 right-0 top-0 z-nav flex h-16 items-center justify-end gap-8 bg-neutral-900 pl-4 pr-8">
<div class="absolute left-72 flex items-center gap-2 px-4 text-sm text-neutral-500">
{#if $hud.pending.length > 0}
<i class="fa fa-circle-notch fa-spin" />
Sending {quantify($hud.pending.length, "note")}.
{:else if $hud.failure.length > 0}
<i class="fa fa-triangle-exclamation" />
Failed to publish {quantify($hud.failure.length, "note")}.
{:else if $hud.success.length > 0}
<i class="fa fa-check" />
Successfully published {quantify($hud.success.length, "note")}.
{:else}
<i class="fa fa-check" />
No recent notes.
{/if}
<Anchor underline modal href="/publishes">Details</Anchor>
</div>
<div class="relative"> <div class="relative">
<div class="flex"> <div class="flex">
<Input <Input

View File

@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import {NAMED_BOOKMARKS} from "@welshman/util" import {NAMED_BOOKMARKS, addressToNaddr, decodeAddress} from "@welshman/util"
import FlexColumn from "src/partials/FlexColumn.svelte" import FlexColumn from "src/partials/FlexColumn.svelte"
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import Chip from "src/partials/Chip.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import CopyValueSimple from "src/partials/CopyValueSimple.svelte"
import FeedSummary from "src/app/shared/FeedSummary.svelte" import FeedSummary from "src/app/shared/FeedSummary.svelte"
import {readFeed, readList, displayFeed, mapListToFeed} from "src/domain" import {readFeed, readList, displayFeed, mapListToFeed} from "src/domain"
import {repository} from "src/engine" import {repository} from "src/engine"
@ -12,6 +14,7 @@
export let address export let address
const event = repository.getEvent(address) const event = repository.getEvent(address)
const deleted = repository.isDeleted(event)
const feed = address.startsWith(NAMED_BOOKMARKS) const feed = address.startsWith(NAMED_BOOKMARKS)
? mapListToFeed(readList(event)) ? mapListToFeed(readList(event))
: readFeed(event) : readFeed(event)
@ -27,15 +30,27 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="staatliches flex items-center gap-3 text-xl"> <span class="staatliches flex items-center gap-3 text-xl">
<i class="fa fa-rss" /> <i class="fa fa-rss" />
<Anchor on:click={loadFeed} class={feed.title ? "" : "text-neutral-500"}> <span class:text-neutral-400={!feed.title} class:line-through={deleted}>
{displayFeed(feed)} {displayFeed(feed)}
</Anchor>
</span> </span>
<slot name="controls" /> {#if deleted}
<Chip danger small>Deleted</Chip>
{/if}
</span>
<slot name="controls">
<Anchor on:click={loadFeed}>
Load feed
</Anchor>
</slot>
</div> </div>
{#if feed.description} {#if feed.description}
<p>{feed.description}</p> <p>{feed.description}</p>
{/if} {/if}
<div class="flex items-start justify-between">
<FeedSummary feed={feed.definition} /> <FeedSummary feed={feed.definition} />
<div class="py-2">
<CopyValueSimple label="Feed address" value={addressToNaddr(decodeAddress(address))} />
</div>
</div>
</FlexColumn> </FlexColumn>
</Card> </Card>

View File

@ -117,12 +117,7 @@
{#if listMenuIsOpen} {#if listMenuIsOpen}
<Popover2 absolute hideOnClick onClose={closeListMenu} class="right-0 top-8 w-60"> <Popover2 absolute hideOnClick onClose={closeListMenu} class="right-0 top-8 w-60">
<Menu> <Menu>
<MenuItem inert class="flex items-center justify-between bg-neutral-800 shadow"> <MenuItem inert class="staatliches bg-neutral-800 text-lg shadow">Your Feeds</MenuItem>
<span class="staatliches text-lg">Your Feeds</span>
<Anchor href={router.at("feeds").toString()}>
<i class="fa fa-cog" />
</Anchor>
</MenuItem>
<div class="max-h-96 overflow-auto"> <div class="max-h-96 overflow-auto">
<MenuItem on:click={() => setFeed(followsFeed)}>Follows</MenuItem> <MenuItem on:click={() => setFeed(followsFeed)}>Follows</MenuItem>
<MenuItem on:click={() => setFeed(networkFeed)}>Network</MenuItem> <MenuItem on:click={() => setFeed(networkFeed)}>Network</MenuItem>
@ -136,6 +131,13 @@
{displayList(feed.list)} {displayList(feed.list)}
</MenuItem> </MenuItem>
{/each} {/each}
<div class="h-px bg-neutral-600" />
<MenuItem href={router.at("feeds").toString()} class="flex items-center gap-2">
<i class="fa fa-rss" /> Manage feeds
</MenuItem>
<MenuItem href={router.at("lists").toString()} class="flex items-center gap-2">
<i class="fa fa-list" /> Manage lists
</MenuItem>
</div> </div>
</Menu> </Menu>
</Popover2> </Popover2>

View File

@ -168,7 +168,7 @@
search={searchFeeds} search={searchFeeds}
onChange={addFeed} onChange={addFeed}
displayItem={displayPubkey}> displayItem={displayPubkey}>
<i slot="before" class="fa fa-scroll" /> <i slot="before" class="fa fa-rss" />
<span slot="item" let:item> <span slot="item" let:item>
<PersonBadge inert pubkey={item} /> <PersonBadge inert pubkey={item} />
</span> </span>

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import {quantify} from 'hurdak' import {quantify} from "hurdak"
import {first} from '@welshman/lib' import {first} from "@welshman/lib"
import {Tags} from '@welshman/util' import {Tags, addressToNaddr, decodeAddress} from "@welshman/util"
import {defaultTagFeedMappings} from '@welshman/feeds' import {defaultTagFeedMappings} from "@welshman/feeds"
import FlexColumn from "src/partials/FlexColumn.svelte" import FlexColumn from "src/partials/FlexColumn.svelte"
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
@ -14,6 +14,7 @@
import {router} from "src/app/util" import {router} from "src/app/util"
export let address export let address
export let inert = false
const tagTypes = defaultTagFeedMappings.map(first) as string[] const tagTypes = defaultTagFeedMappings.map(first) as string[]
const event = repository.getEvent(address) const event = repository.getEvent(address)
@ -22,9 +23,11 @@
const list = readList(event) const list = readList(event)
const loadFeed = () => { const loadFeed = () => {
if (!inert) {
globalFeed.set(mapListToFeed(list)) globalFeed.set(mapListToFeed(list))
router.at("notes").push() router.at("notes").push()
} }
}
</script> </script>
<Card> <Card>
@ -32,25 +35,28 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="staatliches flex items-center gap-3 text-xl"> <span class="staatliches flex items-center gap-3 text-xl">
<i class="fa fa-list" /> <i class="fa fa-list" />
<span <span class:text-neutral-400={!list.title} class:line-through={deleted}>
class:text-neutral-400={!list.title}
class:line-through={deleted}>
<Anchor on:click={loadFeed}>
{displayList(list)} {displayList(list)}
</Anchor>
</span> </span>
{#if deleted} {#if deleted}
<Chip danger small>Deleted</Chip> <Chip danger small>Deleted</Chip>
{/if} {/if}
</span> </span>
<slot name="controls" /> <slot name="controls">
<Anchor on:click={loadFeed}>
Load as feed
</Anchor>
</div>
</div> </div>
{#if list.description} {#if list.description}
<p>{list.description}</p> <p>{list.description}</p>
{/if} {/if}
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
{quantify(tags.filterByKey(tagTypes).count(), 'item')} {quantify(tags.filterByKey(tagTypes).count(), "item")}
<CopyValueSimple label="List address" value={address} class="text-neutral-400" /> <CopyValueSimple
label="List address"
value={addressToNaddr(decodeAddress(address))}
class="text-neutral-400" />
</div> </div>
</FlexColumn> </FlexColumn>
</Card> </Card>

View File

@ -45,6 +45,7 @@
const submit = async () => { const submit = async () => {
const relays = hints.WriteRelays().getUrls() const relays = hints.WriteRelays().getUrls()
const template = list.event ? editList(list) : createList(list) const template = list.event ? editList(list) : createList(list)
console.log(template)
const pub = await createAndPublish({...template, relays}) const pub = await createAndPublish({...template, relays})
showInfo("Your list has been saved!") showInfo("Your list has been saved!")

View File

@ -16,13 +16,14 @@
import NoteContentKind30311 from "src/app/shared/NoteContentKind30311.svelte" import NoteContentKind30311 from "src/app/shared/NoteContentKind30311.svelte"
import NoteContentKind30402 from "src/app/shared/NoteContentKind30402.svelte" import NoteContentKind30402 from "src/app/shared/NoteContentKind30402.svelte"
import NoteContentKind31337 from "src/app/shared/NoteContentKind31337.svelte" import NoteContentKind31337 from "src/app/shared/NoteContentKind31337.svelte"
import NoteContentKind31890 from "src/app/shared/NoteContentKind31890.svelte"
import NoteContentKind31923 from "src/app/shared/NoteContentKind31923.svelte" import NoteContentKind31923 from "src/app/shared/NoteContentKind31923.svelte"
import NoteContentKind32123 from "src/app/shared/NoteContentKind32123.svelte" import NoteContentKind32123 from "src/app/shared/NoteContentKind32123.svelte"
import NoteContentKind34550 from "src/app/shared/NoteContentKind34550.svelte" import NoteContentKind34550 from "src/app/shared/NoteContentKind34550.svelte"
import NoteContentKind35834 from "src/app/shared/NoteContentKind35834.svelte" import NoteContentKind35834 from "src/app/shared/NoteContentKind35834.svelte"
import NoteContentKindList from "src/app/shared/NoteContentKindList.svelte" import NoteContentKindList from "src/app/shared/NoteContentKindList.svelte"
import {getSetting} from "src/engine" import {getSetting} from "src/engine"
import {LIST_KINDS} from 'src/domain' import {LIST_KINDS} from "src/domain"
export let note export let note
export let isQuote = false export let isQuote = false
@ -74,6 +75,8 @@
<NoteContentKind30402 {note} {showEntire} {showMedia} /> <NoteContentKind30402 {note} {showEntire} {showMedia} />
{:else if note.kind === 31337} {:else if note.kind === 31337}
<NoteContentKind31337 {note} {showMedia} /> <NoteContentKind31337 {note} {showMedia} />
{:else if note.kind === 31890}
<NoteContentKind31890 {note} />
{:else if note.kind === 31923} {:else if note.kind === 31923}
<NoteContentKind31923 {note} /> <NoteContentKind31923 {note} />
{:else if note.kind === 32123} {:else if note.kind === 32123}

View File

@ -0,0 +1,8 @@
<script lang="ts">
import {getAddress} from "@welshman/util"
import FeedCard from "src/app/shared/FeedCard.svelte"
export let note
</script>
<FeedCard address={getAddress(note)} />

View File

@ -40,7 +40,7 @@
actions.push({ actions.push({
onClick: () => router.at("lists/select").qp({type: "p", value: pubkey}).open(), onClick: () => router.at("lists/select").qp({type: "p", value: pubkey}).open(),
label: "Add to list", label: "Add to list",
icon: "scroll", icon: "list",
}) })
} }

View File

@ -45,7 +45,7 @@
actions.push({ actions.push({
onClick: () => router.at("lists/select").qp({type: "r", value: url}).open(), onClick: () => router.at("lists/select").qp({type: "r", value: url}).open(),
label: "Add to list", label: "Add to list",
icon: "scroll", icon: "list",
}) })
actions.push({ actions.push({

View File

@ -14,7 +14,7 @@
actions.push({ actions.push({
onClick: () => router.at("lists/select").qp({type: "t", value: topic}).open(), onClick: () => router.at("lists/select").qp({type: "t", value: topic}).open(),
label: "Add to list", label: "Add to list",
icon: "scroll", icon: "list",
}) })
} }
} }

View File

@ -43,5 +43,5 @@
</div> </div>
{/each} {/each}
{#if $userFeeds.length === 0 && $userListFeeds.length === 0} {#if $userFeeds.length === 0 && $userListFeeds.length === 0}
<p class="py-12 text-center">No feeds found.</p> <p class="py-12 text-center">You don't have any lists yet.</p>
{/if} {/if}

View File

@ -1,13 +1,17 @@
<script lang="ts"> <script lang="ts">
import Subheading from 'src/partials/Subheading.svelte' import Subheading from "src/partials/Subheading.svelte"
import ListForm from "src/app/shared/ListForm.svelte" import ListForm from "src/app/shared/ListForm.svelte"
import {router} from "src/app/util" import {router} from "src/app/util"
import {makeList} from "src/domain" import {makeList} from "src/domain"
const list = makeList() export let tags = []
const list = makeList({tags})
const hide = tags.length > 0 ? ["type"] : []
const exit = () => router.clearModals() const exit = () => router.clearModals()
</script> </script>
<Subheading class="text-center">Create list</Subheading> <Subheading class="text-center">Create list</Subheading>
<ListForm {list} {exit} /> <ListForm {list} {exit} {hide} />

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import {uniqBy, nth} from "@welshman/lib"
import Subheading from "src/partials/Subheading.svelte" import Subheading from "src/partials/Subheading.svelte"
import ListForm from "src/app/shared/ListForm.svelte" import ListForm from "src/app/shared/ListForm.svelte"
import {router} from "src/app/util" import {router} from "src/app/util"
@ -6,15 +7,18 @@
import {repository} from "src/engine" import {repository} from "src/engine"
export let address export let address
export let tags = []
const event = repository.getEvent(address) const event = repository.getEvent(address)
const list = {...readList(event), tags: uniqBy(nth(1), [...event.tags, ...tags])}
const exit = () => router.clearModals() const exit = () => router.clearModals()
</script> </script>
{#if event} {#if event}
<Subheading class="text-center">Edit list</Subheading> <Subheading class="text-center">Edit list</Subheading>
<ListForm showDelete list={readList(event)} {exit} /> <ListForm showDelete {list} {exit} hide={["type"]} />
{:else} {:else}
<p class="text-center">Sorry, we weren't able to find that list.</p> <p class="text-center">Sorry, we weren't able to find that list.</p>
{/if} {/if}

View File

@ -16,7 +16,7 @@
<i class="fa fa-plus" /> List <i class="fa fa-plus" /> List
</Anchor> </Anchor>
</div> </div>
{#each $userLists as list} {#each $userLists as list (getAddress(list.event))}
{@const address = getAddress(list.event)} {@const address = getAddress(list.event)}
<div in:fly={{y: 20}}> <div in:fly={{y: 20}}>
<ListCard {address}> <ListCard {address}>
@ -29,5 +29,5 @@
</div> </div>
{/each} {/each}
{#if $userLists.length === 0} {#if $userLists.length === 0}
<p class="py-12 text-center">No lists found.</p> <p class="py-12 text-center">You don't have any lists yet.</p>
{/if} {/if}

View File

@ -1,39 +1,55 @@
<script lang="ts"> <script lang="ts">
import {append, randomId} from "@welshman/lib" import {first} from "@welshman/lib"
import {getAddress} from "@welshman/util" import {getAddress, Tags} from "@welshman/util"
import {updateIn} from "src/util/misc" import {defaultTagFeedMappings} from "@welshman/feeds"
import {quantify} from "hurdak"
import Subheading from "src/partials/Subheading.svelte" import Subheading from "src/partials/Subheading.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Content from "src/partials/Content.svelte" import Card from "src/partials/Card.svelte"
import ListCard from "src/app/shared/ListCard.svelte" import FlexColumn from "src/partials/FlexColumn.svelte"
import {router} from "src/app/util/router" import {router} from "src/app/util/router"
import {pubkey, userLists} from "src/engine" import {userLists} from "src/engine"
import {displayList} from "src/domain"
export let type export let type
export let value export let value
const label = type === "p" ? "person" : "topic" const tags = [[type, value]]
const modifyList = updateIn("tags", tags => append([type, value], tags)) const tagTypes = defaultTagFeedMappings.map(first) as string[]
const newList = () => ({address: `30003:${$pubkey}:${randomId()}`, tags: []}) const createList = () => router.at("lists/create").cx({tags}).replaceModal()
const selectlist = list => router.at("lists").of(getAddress(list.event)).at("edit").replaceModal() const selectList = list =>
router.at("lists").of(getAddress(list.event)).at("edit").cx({tags}).replaceModal()
</script> </script>
<Content size="lg"> <FlexColumn>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<Subheading>Select a List</Subheading> <Subheading>Select a List</Subheading>
<Anchor button accent on:click={() => selectlist(newList())}> <Anchor button accent on:click={createList}>
<i class="fa fa-plus" /> List <i class="fa fa-plus" /> List
</Anchor> </Anchor>
</div> </div>
<p> <p>Select a list to add your selection to.</p>
Select a list to modify. The selected {label} will be added to it as an additional filter.
</p>
{#each $userLists as list (getAddress(list.event))} {#each $userLists as list (getAddress(list.event))}
<ListCard address={getAddress(list.event)} /> <Card interactive on:click={() => selectList(list)}>
<FlexColumn>
<div class="flex items-center justify-between">
<span class="staatliches flex items-center gap-3 text-xl">
<i class="fa fa-list" />
<span class:text-neutral-400={!list.title}>
{displayList(list)}
</span>
</span>
</div>
{#if list.description}
<p>{list.description}</p>
{/if}
{quantify(Tags.wrap(list.tags).filterByKey(tagTypes).count(), "item")}
</FlexColumn>
</Card>
{:else} {:else}
<p class="text-center py-12">You don't have any custom lists yet.</p> <p class="text-center py-12">You don't have any lists yet.</p>
{/each} {/each}
</Content> </FlexColumn>

View File

@ -13,8 +13,10 @@
$: recent = $publishes.filter(p => p.created_at > now() - seconds(24, "hour")) $: recent = $publishes.filter(p => p.created_at > now() - seconds(24, "hour"))
$: relays = new Set(recent.flatMap(({request}) => request.relays)) $: relays = new Set(recent.flatMap(({request}) => request.relays))
$: pending = recent.filter(p => hasStatus(p, [PublishStatus.Pending]))
$: success = recent.filter(p => hasStatus(p, [PublishStatus.Success])) $: success = recent.filter(p => hasStatus(p, [PublishStatus.Success]))
$: pending = recent.filter(
p => hasStatus(p, [PublishStatus.Pending]) && !hasStatus(p, [PublishStatus.Success]),
)
</script> </script>
<Subheading>Published Events</Subheading> <Subheading>Published Events</Subheading>

View File

@ -1094,7 +1094,7 @@ export const feeds = repository.filter([{kinds: [FEED]}]).derived($events => $ev
export const userFeeds = new Derived([feeds, pubkey], ([$feeds, $pubkey]: [Feed[], string]) => export const userFeeds = new Derived([feeds, pubkey], ([$feeds, $pubkey]: [Feed[], string]) =>
sortBy( sortBy(
prop("title"), f => f.title.toLowerCase(),
$feeds.filter(feed => feed.event.pubkey === $pubkey), $feeds.filter(feed => feed.event.pubkey === $pubkey),
), ),
) )
@ -1105,7 +1105,7 @@ export const lists = repository
export const userLists = new Derived([lists, pubkey], ([$lists, $pubkey]: [List[], string]) => export const userLists = new Derived([lists, pubkey], ([$lists, $pubkey]: [List[], string]) =>
sortBy( sortBy(
prop("title"), l => l.title.toLowerCase(),
$lists.filter(list => list.event.pubkey === $pubkey), $lists.filter(list => list.event.pubkey === $pubkey),
), ),
) )
@ -1120,7 +1120,7 @@ export const userListFeeds = new Derived(
[listFeeds, pubkey], [listFeeds, pubkey],
([$listFeeds, $pubkey]: [Feed[], string]) => ([$listFeeds, $pubkey]: [Feed[], string]) =>
sortBy( sortBy(
prop("title"), l => l.title.toLowerCase(),
$listFeeds.filter(feed => feed.list.event.pubkey === $pubkey), $listFeeds.filter(feed => feed.list.event.pubkey === $pubkey),
), ),
) )

View File

@ -15,7 +15,11 @@
const share = () => router.at("qrcode").at(value).open() const share = () => router.at("qrcode").at(value).open()
</script> </script>
<div class={cx($$props.class, "flex items-center gap-2")}> <div class={cx($$props.class, "flex items-center gap-1")}>
<i class="fa-solid fa-copy cursor-pointer" on:click={copy} /> <div class="cursor-pointer px-1 text-neutral-400 transition-colors hover:text-neutral-100">
<i class="fa-solid fa-qrcode cursor-pointer" on:click={share} /> <i class="fa-solid fa-copy" on:click={copy} />
</div>
<div class="cursor-pointer px-1 text-neutral-400 transition-colors hover:text-neutral-100">
<i class="fa-solid fa-qrcode" on:click={share} />
</div>
</div> </div>

View File

@ -18,6 +18,8 @@ import {
WRAP_NIP04, WRAP_NIP04,
ZAP_RESPONSE, ZAP_RESPONSE,
Tags, Tags,
decodeAddress,
addressToNaddr,
} from "@welshman/util" } from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {schnorr} from "@noble/curves/secp256k1" import {schnorr} from "@noble/curves/secp256k1"
@ -146,6 +148,11 @@ export const parseAnything = async entity => {
} }
} }
// Interpret addresses as naddrs
if (entity.match(/^\d+:\w+:.*$/)) {
entity = addressToNaddr(decodeAddress(entity))
}
try { try {
return nip19.decode(entity) return nip19.decode(entity)
} catch (e) { } catch (e) {