Add space home page

This commit is contained in:
Jon Staab
2024-10-31 12:24:40 -07:00
parent df947e9fcf
commit 74f9531c5f
34 changed files with 401 additions and 155 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
const submit = () => {
onSubmit({
content: $editor.getText({blockSeparator: '\n'}),
content: $editor.getText({blockSeparator: "\n"}),
tags: getEditorTags($editor),
})
+5 -10
View File
@@ -5,7 +5,6 @@
import {deriveProfile, deriveProfileDisplay, formatTimestampAsTime, pubkey} from "@welshman/app"
import type {Thunk} from "@welshman/app"
import {isMobile} from "@lib/html"
import {slideAndFade, conditionalTransition} from "@lib/transition"
import LongPress from "@lib/components/LongPress.svelte"
import Avatar from "@lib/components/Avatar.svelte"
import Link from "@lib/components/Link.svelte"
@@ -36,16 +35,13 @@
const rootEvent = rootId ? deriveEvent(rootId, rootHints) : readable(null)
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const transition = conditionalTransition(thunk, slideAndFade)
const onClick = () => {
const root = $rootEvent || event
pushDrawer(ChannelConversation, {url, room, event: root})
}
const onLongPress = () =>
pushModal(ChannelMessageMenuMobile, {url, event})
const onLongPress = () => pushModal(ChannelMessageMenuMobile, {url, event})
const onReactionClick = (content: string, events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)
@@ -66,14 +62,13 @@
<LongPress
on:click={isMobile || inert ? null : onClick}
onLongPress={inert ? null : onLongPress}
class="group relative flex w-full flex-col gap-1 p-2 text-left transition-colors {inert ? 'hover:bg-base-300' : ''}">
class="group relative flex w-full flex-col gap-1 p-2 text-left transition-colors {inert
? 'hover:bg-base-300'
: ''}">
<div class="flex w-full gap-3">
{#if showPubkey}
<Link external href={pubkeyLink(event.pubkey)} class="flex items-start">
<Avatar
src={$profile?.picture}
class="border border-solid border-base-content"
size={10} />
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={10} />
</Link>
{:else}
<div class="w-10 min-w-10 max-w-10" />
+2 -2
View File
@@ -10,7 +10,7 @@
<script lang="ts">
import {onMount} from "svelte"
import {derived, writable} from "svelte/store"
import {int, assoc, MINUTE, now, sortBy, remove} from "@welshman/lib"
import {int, assoc, MINUTE, sortBy, remove} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {createEvent, DIRECT_MESSAGE} from "@welshman/util"
import {
@@ -84,7 +84,7 @@
id,
type: "note",
value: event,
showPubkey: ((created_at - previousCreatedAt) > int(15, MINUTE)) || previousPubkey !== pubkey,
showPubkey: created_at - previousCreatedAt > int(15, MINUTE) || previousPubkey !== pubkey,
})
previousDate = date
+13 -11
View File
@@ -81,23 +81,25 @@
</button>
</Tippy>
<div class="flex flex-col">
<LongPress class="chat-bubble mx-1 max-w-sm text-left flex flex-col gap-1" onLongPress={showMobileMenu}>
<LongPress
class="chat-bubble mx-1 flex max-w-sm flex-col gap-1 text-left"
onLongPress={showMobileMenu}>
{#if showPubkey && event.pubkey !== $pubkey}
<div class="flex items-center gap-2">
<Link external href={pubkeyLink(event.pubkey)} class="flex gap-1 items-center">
<Link external href={pubkeyLink(event.pubkey)} class="flex items-center gap-1">
<Avatar
src={$profile?.picture}
class="border border-solid border-base-content"
size={4} />
<div class="flex items-center gap-2">
<Link
external
href={pubkeyLink(event.pubkey)}
class="text-sm font-bold"
style="color: {colorValue}">
{$profileDisplay}
</Link>
</div>
<div class="flex items-center gap-2">
<Link
external
href={pubkeyLink(event.pubkey)}
class="text-sm font-bold"
style="color: {colorValue}">
{$profileDisplay}
</Link>
</div>
</Link>
<span class="text-xs opacity-50">{formatTimestampAsTime(event.created_at)}</span>
</div>
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import Confirm from "@lib/components/Confirm.svelte"
import {publishDelete} from "@app/commands"
import {clearModals} from '@app/modal'
import {clearModals} from "@app/modal"
export let url
export let event
+1 -11
View File
@@ -57,14 +57,6 @@
const isStartOrEnd = (i: number) => Boolean(isBoundary(i - 1) || isBoundary(i + 1))
const isBlock = (i: number) => {
const parsed = fullContent[i]
return isEvent(parsed) || isAddress(parsed) || isLink(parsed)
}
const isNextToBlock = (i: number) => isBlock(i - 1) || isBlock(i + 1)
const ignoreWarning = () => {
warning = null
}
@@ -132,9 +124,7 @@
{/if}
{:else if isEllipsis(parsed) && expandInline}
{@html renderParsed(parsed)}
<button type="button" class="underline text-sm">
Read more
</button>
<button type="button" class="text-sm underline"> Read more </button>
{:else}
{@html renderParsed(parsed)}
{/if}
+1 -1
View File
@@ -32,7 +32,7 @@
}
// If we found this event on a relay that the user is a member of, redirect internally
$: localHref = getLocalHref($event)
$: localHref = $event ? getLocalHref($event) : null
$: href = localHref || entityLink(entity)
</script>
+1 -1
View File
@@ -41,7 +41,7 @@
const kind = isAllDay ? EVENT_DATE : EVENT_TIME
const event = createEvent(kind, {
content: $editor.getText({blockSeparator: '\n'}),
content: $editor.getText({blockSeparator: "\n"}),
tags: [
["d", randomId()],
["title", title],
+8 -7
View File
@@ -10,14 +10,14 @@
<div slot="title">What is a bunker link?</div>
</ModalHeader>
<p>
<Link external class="link" href="https://nostr.com/">Nostr</Link> uses "keys" instead of
passwords to identify users. This allows users to own their social identity instead of
renting it from a tech company, and can bring it with them from app to app.
<Link external class="link" href="https://nostr.com/">Nostr</Link> uses "keys" instead of passwords
to identify users. This allows users to own their social identity instead of renting it from a tech
company, and can bring it with them from app to app.
</p>
<p>
A good way to manage your keys is to use a remote signing application. These apps can hold
your keys and log you in remotely to as many applications as you like, without risking
loss or theft of your keys.
A good way to manage your keys is to use a remote signing application. These apps can hold your
keys and log you in remotely to as many applications as you like, without risking loss or theft
of your keys.
</p>
<p>
One way to log in with a remote signer is using a "bunker link" which is more secure and
@@ -25,7 +25,8 @@
copy it into {PLATFORM_NAME}, and you should be good to go!
</p>
<p>
If you don't have a signer yet, <Link external class="link" href="https://nsec.app/">nsec.app</Link>
If you don't have a signer yet, <Link external class="link" href="https://nsec.app/"
>nsec.app</Link>
is a great way to get started.
</p>
<Button class="btn btn-primary" on:click={() => history.back()}>Got it</Button>
+8 -6
View File
@@ -2,7 +2,6 @@
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {PLATFORM_NAME} from "@app/state"
</script>
<div class="column gap-4">
@@ -10,13 +9,16 @@
<div slot="title">What is nostr?</div>
</ModalHeader>
<p>
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build
social apps that talk to each other. Users own their social identity instead of
renting it from a tech company, and can take it with them.
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build social apps that
talk to each other. Users own their social identity instead of renting it from a tech company, and
can take it with them.
</p>
<p>
If you'd like to learn more about what other apps exist in the nostr ecosystem, please
visit <Link external class="link" href="https://nostrapps.com/">nostrapps.com</Link>.
If you'd like to learn more about what other apps exist in the nostr ecosystem, please visit <Link
external
class="link"
href="https://nostrapps.com/">nostrapps.com</Link
>.
</p>
<p>
To learn more about how to manage your keys, or to set up an account, try
+1 -2
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import {Nip46Broker} from "@welshman/signer"
import {addSession} from "@welshman/app"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
@@ -28,7 +27,7 @@
loading = true
try {
if (!await loginWithNip46(token, {pubkey, relays})) {
if (!(await loginWithNip46(token, {pubkey, relays}))) {
return pushToast({
theme: "error",
message: "Something went wrong, please try again!",
+5
View File
@@ -125,6 +125,11 @@
</div>
<div in:fly={{delay: getDelay(true)}}>
<SecondaryNavItem href={makeSpacePath(url)}>
<Icon icon="home-smile" /> Home
</SecondaryNavItem>
</div>
<div in:fly={{delay: getDelay()}}>
<SecondaryNavItem href={makeSpacePath(url, "threads")}>
<Icon icon="notes-minimalistic" /> Threads
</SecondaryNavItem>
</div>
+47
View File
@@ -0,0 +1,47 @@
<script lang="ts">
import {onMount} from "svelte"
import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util"
import {REACTION} from "@welshman/util"
import {pubkey, load, formatTimestamp} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte"
import Content from "@app/components/Content.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import {publishDelete, publishReaction} from "@app/commands"
export let url
export let event
const onReactionClick = (content: string, events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)
if (reaction) {
publishDelete({relays: [url], event: reaction})
} else {
publishReaction({event, content, relays: [url]})
}
}
const onEmoji = (emoji: NativeEmoji) =>
publishReaction({event, relays: [url], content: emoji.unicode})
onMount(() => {
load({filters: [{kinds: [REACTION], "#e": [event.id]}]})
})
</script>
<NoteCard {event} class="card2 bg-alt">
<Content {event} expandMode="inline" />
<div class="flex w-full justify-between gap-2">
<ReactionSummary {event} {onReactionClick}>
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
<Icon icon="smile-circle" size={4} />
</EmojiButton>
</ReactionSummary>
<p class="whitespace-nowrap text-sm opacity-75">
{formatTimestamp(event.created_at)}
</p>
</div>
</NoteCard>
+47
View File
@@ -0,0 +1,47 @@
<script lang="ts">
import {onMount} from "svelte"
import {sortBy, flatten} from "@welshman/lib"
import {feedFromFilter} from "@welshman/feeds"
import {NOTE, getAncestorTags} from "@welshman/util"
import {deriveEvents} from "@welshman/store"
import {repository, feedLoader} from "@welshman/app"
import {createScroller} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
export let url
export let pubkey
const filter = {kinds: [NOTE], authors: [pubkey]}
const events = deriveEvents(repository, {filters: [filter]})
const loader = feedLoader.getLoader(feedFromFilter(filter), {})
let element: Element
onMount(() => {
const scroller = createScroller({
element,
onScroll: async () => {
const $loader = await loader
$loader(5)
},
})
return () => scroller.stop()
})
</script>
<div class="flex max-w-full flex-col gap-4 p-4" bind:this={element}>
<div class="flex flex-col gap-2">
{#each sortBy(e => -e.created_at, $events) as event (event.id)}
{#if flatten(Object.values(getAncestorTags(event.tags))).length === 0}
<NoteItem {url} {event} />
{/if}
{:else}
<p class="flex center my-12">
<Spinner loading />
</p>
{/each}
</div>
</div>
+52
View File
@@ -0,0 +1,52 @@
<script lang="ts">
import {onMount} from "svelte"
import {sortBy, flatten} from "@welshman/lib"
import {feedFromFilter} from "@welshman/feeds"
import {NOTE, getAncestorTags} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {feedLoader} from "@welshman/app"
import {createScroller} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
export let url
export let pubkey
const filter = {kinds: [NOTE], authors: [pubkey]}
const loader = feedLoader.getLoader(feedFromFilter(filter), {
onEvent: (e: TrustedEvent) => {
events = sortBy(e => -e.created_at, [...events, e])
},
})
let element: Element
let events: TrustedEvent[] = []
onMount(() => {
const scroller = createScroller({
element,
delay: 300,
threshold: 3000,
onScroll: async () => {
const $loader = await loader
$loader(5)
},
})
return () => scroller.stop()
})
</script>
<div class="col-4" bind:this={element}>
<div class="flex flex-col gap-2">
{#each events as event (event.id)}
{#if flatten(Object.values(getAncestorTags(event.tags))).length === 0}
<NoteItem {url} {event} />
{/if}
{/each}
<p class="center my-12 flex">
<Spinner loading />
</p>
</div>
</div>
+2 -1
View File
@@ -17,7 +17,7 @@
</script>
{#if $reactions.length > 0}
<div class="flex gap-2">
<div class="flex min-w-0 flex-wrap gap-2">
{#each groupedReactions.entries() as [content, events]}
{@const isOwn = events.some(e => e.pubkey === $pubkey)}
{@const onClick = () => onReactionClick(content, events)}
@@ -34,5 +34,6 @@
{/if}
</button>
{/each}
<slot />
</div>
{/if}
+1 -1
View File
@@ -46,7 +46,7 @@
<div class="flex flex-wrap items-center justify-between gap-2">
<ReactionSummary {event} {onReactionClick} />
<div class="flex flex-wrap flex-grow justify-end gap-2">
<div class="flex flex-grow flex-wrap justify-end gap-2">
{#if $deleted}
<div class="btn btn-error btn-xs rounded-full">Deleted</div>
{/if}
+9 -11
View File
@@ -3,7 +3,6 @@
import type {Readable} from "svelte/store"
import {writable} from "svelte/store"
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
import {append} from "@welshman/lib"
import {createEvent} from "@welshman/util"
import {publishThunk} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
@@ -26,7 +25,6 @@
const loading = writable(false)
const submit = () => {
if (!title) {
return pushToast({
theme: "error",
@@ -34,7 +32,7 @@
})
}
const content = $editor.getText({blockSeparator: '\n'})
const content = $editor.getText({blockSeparator: "\n"})
if (!content.trim()) {
return pushToast({
@@ -43,11 +41,7 @@
})
}
const tags = [
["title", title],
tagRoom(GENERAL, url),
...getEditorTags($editor),
]
const tags = [["title", title], tagRoom(GENERAL, url), ...getEditorTags($editor)]
publishThunk({
event: createEvent(THREAD, {content, tags}),
@@ -68,7 +62,7 @@
getPubkeyHints,
autofocus: true,
placeholder: "What's on your mind?",
})
}),
)
})
</script>
@@ -78,11 +72,15 @@
<div slot="title">Create a Thread</div>
<div slot="info">Share a link, or start a discussion.</div>
</ModalHeader>
<div class="relative col-8">
<div class="col-8 relative">
<Field>
<p slot="label">Title*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<input bind:value={title} class="grow" type="text" placeholder="What is this thread about?" />
<input
bind:value={title}
class="grow"
type="text"
placeholder="What is this thread about?" />
</label>
</Field>
<Field>
+5 -5
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import {nthEq} from '@welshman/lib'
import {nthEq} from "@welshman/lib"
import {formatTimestamp} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
import Content from "@app/components/Content.svelte"
@@ -12,19 +12,19 @@
export let event
export let hideActions = false
const title = event.tags.find(nthEq(0, 'title'))?.[1]
const title = event.tags.find(nthEq(0, "title"))?.[1]
</script>
<Link class="col-2 card2 bg-alt w-full cursor-pointer" href={makeThreadPath(url, event.id)}>
<div class="flex w-full justify-between items-center gap-2">
<div class="flex w-full items-center justify-between gap-2">
<p class="text-xl">{title}</p>
<p class="text-sm opacity-75">
{formatTimestamp(event.created_at)}
</p>
</div>
<Content {event} expandMode="inline" />
<div class="flex gap-2 items-end justify-between w-full">
<span class="text-sm opacity-75 whitespace-nowrap py-1">
<div class="flex w-full items-end justify-between gap-2">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Posted by
<Link external href={pubkeyLink(event.pubkey)} class="link-content">
@<ProfileName pubkey={event.pubkey} />
+1 -1
View File
@@ -23,7 +23,7 @@
const loading = writable(false)
const submit = () => {
const content = $editor.getText({blockSeparator: '\n'})
const content = $editor.getText({blockSeparator: "\n"})
const tags = append(tagRoom(GENERAL, url), getEditorTags($editor))
if (!content.trim()) {
+1 -1
View File
@@ -7,7 +7,7 @@
import Tippy from "@lib/components/Tippy.svelte"
import Button from "@lib/components/Button.svelte"
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
import {userSettingValues} from '@app/state'
import {userSettingValues} from "@app/state"
export let thunk: Thunk | MergedThunk