Add space home page
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
|
||||
const submit = () => {
|
||||
onSubmit({
|
||||
content: $editor.getText({blockSeparator: '\n'}),
|
||||
content: $editor.getText({blockSeparator: "\n"}),
|
||||
tags: getEditorTags($editor),
|
||||
})
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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!",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user