Add Content and Heading components

This commit is contained in:
Jonathan Staab 2023-01-16 12:02:18 -08:00
parent aaaef18cae
commit c781e88574
31 changed files with 289 additions and 223 deletions

View File

@ -21,6 +21,7 @@ If you like Coracle and want to support its development, you can donate sats via
- [ ] Add followers/follows lists on profile page
- [ ] Image uploads
- Use dufflepud. Default will charge via lightning and have a tos, others can self-host and skip that.
- Add banner field to profile
- [ ] Server discovery and relay publishing https://github.com/nostr-protocol/nips/pull/32/files
- [ ] Support invoices https://twitter.com/jb55/status/1604131336247476224
- [ ] NIP 05
@ -68,19 +69,20 @@ If you like Coracle and want to support its development, you can donate sats via
- [x] Handle failed connections
- [x] Close connections that haven't been used in a while
- [x] Add strategy that callers can opt into to accept first eose from a relay that has any events
- [ ] Login
- [x] Login
- [x] Prefer extension, make private key entry "advanced"
- [x] Buttons should redirect to login modal if using pubkey login
- [ ] We often get the root as the reply, figure out why that is, compared to astral/damus
- [x] Load feeds from network rather than user relays?
- [x] Still use "my" relays for global, this could make global feed more useful
- [x] If we use my relays for global, we don't have to wait for network to load initially
- [ ] Figure out fast vs complete tradeoff. Skipping loadContext speeds things up a ton
- [ ] Figure out migrations from previous version
- [ ] Figure out fast vs complete tradeoff. Skipping loadContext speeds things up a ton.
- [ ] Make loadParents false by default. Maybe make fast the default
- [ ] Figure out how to make threads fast and complete. Load only the note, then preload entire thread in background?
- [ ] Add relays/mentions to note and reply composition
- [ ] Add layout component with max-w, padding, etc. Test on mobile size
- [ ] Add tips to login spinner
- [ ] Add banner
- [ ] Figure out migrations from previous version
- [ ] Fix search
## 0.2.7

View File

@ -51,7 +51,7 @@
menuIsOpen.set(false)
}
const {privkey} = keys
const {canSign} = keys
const {lastCheckedAlerts, mostRecentAlert} = alerts
let menuIcon
@ -120,7 +120,7 @@
<div use:links class="h-full">
<div class="pt-16 text-white h-full lg:ml-56">
<Route path="/alerts" component={Alerts} />
<Route path="/search/:type" component={Search} />
<Route path="/search/:activeTab" component={Search} />
<Route path="/notes/:activeTab" component={Notes} />
<Route path="/notes/new" component={NoteCreate} />
<Route path="/people/:npub/:activeTab" let:params>
@ -225,7 +225,7 @@
{/if}
</div>
{#if $privkey}
{#if $canSign}
<div class="fixed bottom-0 right-0 m-8">
<a
href="/notes/new"

View File

@ -1,6 +1,6 @@
import {last, uniq} from 'ramda'
import {last, objOf, uniq} from 'ramda'
import {derived, get} from 'svelte/store'
import {getTagValues, Tags} from 'src/util/nostr'
import {Tags} from 'src/util/nostr'
import pool from 'src/agent/pool'
import keys from 'src/agent/keys'
import defaults from 'src/agent/defaults'
@ -28,13 +28,13 @@ export const getMuffle = () => {
return []
}
return getTagValues($user.muffle.filter(t => Math.random() < last(t)))
return Tags.wrap($user.muffle.filter(t => Math.random() < last(t))).values().all()
}
export const getFollows = pubkey => {
const person = getPerson(pubkey)
return getTagValues(person?.petnames || defaults.petnames)
return Tags.wrap(person?.petnames || defaults.petnames).values().all()
}
export const getRelays = pubkey => {
@ -53,10 +53,10 @@ export const getRelays = pubkey => {
export const getEventRelays = event => {
if (event.seen_on) {
return [event.seen_on]
return [{url: event.seen_on}]
}
return uniq(getRelays(event.pubkey).concat(Tags.from(event).relays()))
return uniq(getRelays(event.pubkey).concat(Tags.from(event).relays())).map(objOf('url'))
}
export const publish = async (relays, event) => {

View File

@ -6,6 +6,7 @@ let signingFunction
const pubkey = synced('agent/user/pubkey')
const privkey = synced('agent/user/privkey')
const canSign = synced('agent/user/canSign')
const setPrivateKey = _privkey => {
privkey.set(_privkey)
@ -35,10 +36,11 @@ const sign = async event => {
const clear = () => {
pubkey.set(null)
privkey.set(null)
canSign.set(false)
}
// Init signing function by re-setting pubkey
setPublicKey(get(pubkey))
export default {pubkey, privkey, setPrivateKey, setPublicKey, sign, clear}
export default {pubkey, privkey, canSign, setPrivateKey, setPublicKey, sign, clear}

View File

@ -1,4 +1,4 @@
import {join, uniqBy, last} from 'ramda'
import {prop, join, uniqBy, last} from 'ramda'
import {get} from 'svelte/store'
import {first} from "hurdak/lib/hurdak"
import {Tags} from 'src/util/nostr'
@ -29,27 +29,27 @@ const createNote = (relays, content, mentions = []) =>
publishEvent(relays, 1, {content, tags: mentions.map(p => ["p", p, first(getRelays(p))])})
const createReaction = (relays, note, content) => {
const relay = getBestRelay(note)
const {url} = getBestRelay(note)
const tags = uniqBy(
join(':'),
note.tags
.filter(t => ["p", "e"].includes(t[0]))
.map(t => last(t) === 'root' ? t : t.slice(0, -1))
.concat([["p", note.pubkey, relay], ["e", note.id, relay, 'reply']])
.map(t => last(t) === 'reply' ? t.slice(0, -1) : t)
.concat([["p", note.pubkey, url], ["e", note.id, url, 'reply']])
)
return publishEvent(relays, 7, {content, tags})
}
const createReply = (relays, note, content, mentions = []) => {
const relay = getBestRelay(note)
const {url} = getBestRelay(note)
const tags = uniqBy(
join(':'),
note.tags
.filter(t => ["p", "e"].includes(t[0]))
.map(t => last(t) === 'root' ? t : t.slice(0, -1))
.concat([["p", note.pubkey, relay], ["e", note.id, relay, 'reply']])
.concat(mentions.map(p => ["p", p, first(getRelays(p))]))
.map(t => last(t) === 'reply' ? t.slice(0, -1) : t)
.concat([["p", note.pubkey, url], ["e", note.id, url, 'reply']])
.concat(mentions.map(p => ["p", p, prop('url', first(getRelays(p)))]))
)
return publishEvent(relays, 1, {content, tags})
@ -86,7 +86,7 @@ const publishEvent = (relays, kind, {content = '', tags = []} = {}) => {
const createdAt = Math.round(new Date().valueOf() / 1000)
const event = {kind, content, tags, pubkey, created_at: createdAt}
console.log(`publishing ${JSON.stringify(event)} to ${JSON.stringify(relays)}`)
console.log("Publishing", event, relays)
return publish(relays, event)
}

View File

@ -13,13 +13,17 @@ import loaders from 'src/app/loaders'
export {toast, modal, settings, alerts}
export const login = async ({privkey, pubkey}) => {
export const login = async ({privkey, pubkey}, usingExtension = false) => {
if (privkey) {
keys.setPrivateKey(privkey)
} else {
keys.setPublicKey(pubkey)
}
if (usingExtension || privkey) {
keys.canSign.set(true)
}
// Load network and start listening, but don't wait for it
loaders.loadNetwork(getRelays(), pubkey),
alerts.load(getRelays(), pubkey),
@ -80,7 +84,7 @@ export const loadNote = async (relays, id) => {
return null
}
return annotate(found, await loaders.loadContext(relays, found))
return annotate(found, await loaders.loadContext(relays, found, {mode: 'fast', loadChildren: true}))
}
export const render = (note, {showEntire = false}) => {

View File

@ -1,6 +1,6 @@
import {uniqBy, prop, uniq, flatten, pluck, identity} from 'ramda'
import {propEq, uniqBy, prop, uniq, flatten, pluck, identity} from 'ramda'
import {ensurePlural, createMap, chunk} from 'hurdak/lib/hurdak'
import {findReply, personKinds, Tags, getTagValues} from 'src/util/nostr'
import {findReply, personKinds, Tags} from 'src/util/nostr'
import {now, timedelta} from 'src/util/misc'
import {load, getPerson} from 'src/agent'
import defaults from 'src/agent/defaults'
@ -44,7 +44,7 @@ const loadNetwork = async (relays, pubkey) => {
await loadPeople(tags.relays(), tags.values().all(), {mode: 'fast'})
}
const loadContext = async (relays, notes, {loadParents = true, ...opts} = {}) => {
const loadContext = async (relays, notes, {loadParents = true, loadChildren = false, ...opts} = {}) => {
notes = ensurePlural(notes)
if (notes.length === 0) {
@ -54,7 +54,7 @@ const loadContext = async (relays, notes, {loadParents = true, ...opts} = {}) =>
return flatten(await Promise.all(
chunk(256, notes).map(async chunk => {
const authors = getStalePubkeys(pluck('pubkey', notes))
const parentTags = loadParents ? uniq(notes.map(findReply).filter(identity)) : []
const parentTags = uniq(notes.map(findReply).filter(identity))
const combinedRelays = uniq(relays.concat(Tags.wrap(parentTags).relays()))
const filter = [{kinds: [1, 7], '#e': pluck('id', notes)}]
@ -62,27 +62,29 @@ const loadContext = async (relays, notes, {loadParents = true, ...opts} = {}) =>
filter.push({kinds: personKinds, authors})
}
if (parentTags.length > 0) {
filter.push({kinds: [1], ids: getTagValues(parentTags)})
if (loadParents && parentTags.length > 0) {
filter.push({kinds: [1], ids: Tags.wrap(parentTags).values().all()})
}
const events = await load(combinedRelays, filter, opts)
let events = await load(combinedRelays, filter, opts)
if (parentTags.length === 0) {
return events
const children = events.filter(propEq('kind', 1))
const childRelays = Tags.from(children).relays()
if (loadChildren && children.length > 0) {
events = events.concat(await loadContext(childRelays, children, {loadParents: false, ...opts}))
}
const eventsById = createMap('id', events)
const parents = getTagValues(parentTags).map(id => eventsById[id]).filter(identity)
const parentRelays = Tags.from(parents).relays()
if (loadParents && parentTags.length > 0) {
const eventsById = createMap('id', events)
const parents = Tags.wrap(parentTags).values().all().map(id => eventsById[id]).filter(identity)
const parentRelays = Tags.from(parents).relays()
events = events.concat(await loadContext(parentRelays, parents, {loadParents: false, ...opts}))
}
// We're recurring and so may end up with duplicates here
return uniqBy(
prop('id'),
events.concat(
await loadContext(parentRelays, parents, {loadParents: false, ...opts})
)
)
return uniqBy(prop('id'), events)
})
))
}

View File

@ -0,0 +1,23 @@
<script>
import cx from 'classnames'
export let size = "2xl"
const className = "flex flex-col m-auto text-white gap-8"
if (!['lg', '2xl'].includes(size)) {
throw new Error(`Invalid size: ${size}`)
}
</script>
{#if size === 'lg'}
<div {...$$props} class={cx($$props.class, className, "p-2 py-16 max-w-lg")}>
<slot />
</div>
{/if}
{#if size === '2xl'}
<div {...$$props} class={cx($$props.class, className, "py-8 p-4 max-w-2xl")}>
<slot />
</div>
{/if}

View File

@ -0,0 +1,5 @@
<script>
import cx from 'classnames'
</script>
<h1 {...$$props} class={cx($$props.class, "staatliches text-6xl my-4")}><slot /></h1>

View File

@ -5,6 +5,7 @@
import {quantify} from 'hurdak/lib/hurdak'
import {createScroller} from 'src/util/misc'
import Spinner from 'src/partials/Spinner.svelte'
import Content from 'src/partials/Content.svelte'
import Note from "src/partials/Note.svelte"
import {modal} from "src/app"
@ -48,7 +49,7 @@
})
</script>
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
<Content>
{#if newNotes.length > 0}
<div
in:slide
@ -58,8 +59,8 @@
</div>
{/if}
{#each notes as note (note.id)}
<li><Note {note} {depth} /></li>
<Note {note} {depth} />
{/each}
</ul>
</Content>
<Spinner />

View File

@ -7,7 +7,7 @@
export let setActiveTab
</script>
<ul class="border-b border-solid border-dark flex max-w-xl m-auto pt-2" in:fly={{y: 20}}>
<ul class="border-b border-solid border-dark flex 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"

View File

@ -2,6 +2,8 @@
import {fly} from 'svelte/transition'
import {toast, modal, addRelay} from "src/app"
import Input from 'src/partials/Input.svelte'
import Content from 'src/partials/Content.svelte'
import Heading from 'src/partials/Heading.svelte'
import Button from 'src/partials/Button.svelte'
let url = $modal.url
@ -29,10 +31,10 @@
}
</script>
<form on:submit={submit} class="flex justify-center py-8 px-4 text-white" in:fly={{y: 20}}>
<div class="flex flex-col gap-4 max-w-2xl flex-grow">
<form on:submit={submit} in:fly={{y: 20}}>
<Content>
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">Add a relay</h1>
<Heading>Add a relay</Heading>
</div>
<div class="flex flex-col gap-8 w-full">
<div class="flex flex-col gap-1">
@ -46,5 +48,5 @@
</div>
<Button type="submit" class="text-center">Done</Button>
</div>
</div>
</Content>
</form>

View File

@ -7,6 +7,7 @@
import {user, db} from 'src/agent'
import {alerts} from 'src/app'
import Note from 'src/partials/Note.svelte'
import Content from 'src/partials/Content.svelte'
import Like from 'src/partials/Like.svelte'
let limit = 0
@ -42,12 +43,14 @@
})
</script>
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
<Content>
{#each annotatedNotes as e (e.id)}
{#if e.people}
<li in:fly={{y: 20}}><Like note={e} /></li>
{:else}
<li in:fly={{y: 20}}><Note note={e} /></li>
{/if}
<div in:fly={{y: 20}}>
{#if e.people}
<Like note={e} />
{:else}
<Note note={e} />
{/if}
</div>
{/each}
</ul>
</Content>

View File

@ -10,15 +10,13 @@
const relays = (data.relays || []).map(objOf('url'))
</script>
<div class="py-4 max-w-xl m-auto">
{#if type === "nevent"}
<NoteDetail note={{id: data.id}} {relays} />
{:else if type === "note"}
<NoteDetail note={{id: data}} />
{:else if type === "nprofile"}
<Person npub={nip19.npubEncode(data.pubkey)} {relays} activeTab="notes" />
{:else if type === "npub"}
<Person npub={entity} activeTab="notes" />
{/if}
</div>
{#if type === "nevent"}
<NoteDetail note={{id: data.id}} {relays} />
{:else if type === "note"}
<NoteDetail note={{id: data}} />
{:else if type === "nprofile"}
<Person npub={nip19.npubEncode(data.pubkey)} {relays} activeTab="notes" />
{:else if type === "npub"}
<Person npub={entity} activeTab="notes" />
{/if}

View File

@ -6,6 +6,8 @@
import {copyToClipboard} from "src/util/html"
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Content from "src/partials/Content.svelte"
import Heading from 'src/partials/Heading.svelte'
import {keys} from "src/agent"
import {toast} from "src/app"
@ -30,10 +32,10 @@
})
</script>
<div class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
<div class="flex flex-col gap-4 max-w-2xl">
<div in:fly={{y: 20}}>
<Content>
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">Your Keys</h1>
<Heading>Your Keys</Heading>
<p>
Your account is identified across the network using
a public/private <Anchor href={keypairUrl} external>keypair</Anchor>.
@ -67,5 +69,5 @@
</div>
{/if}
</div>
</div>
</Content>
</div>

View File

@ -1,13 +1,15 @@
<script>
import {fly} from 'svelte/transition'
import Anchor from "src/partials/Anchor.svelte"
import Content from "src/partials/Content.svelte"
import Heading from 'src/partials/Heading.svelte'
import {modal, login} from "src/app"
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
const autoLogIn = async () => {
if (window.nostr) {
await login({pubkey: await window.nostr.getPublicKey()})
await login({pubkey: await window.nostr.getPublicKey()}, true)
} else {
modal.set({form: 'privkeyLogin'})
}
@ -22,25 +24,27 @@
}
</script>
<div class="m-auto max-w-2xl p-12" in:fly={{y: 20}}>
<div class="flex flex-col gap-8 max-w-2xl">
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">Welcome!</h1>
<i>To the Nostr Protocol</i>
</div>
<p class="text-center">
To log in to existing account, simply click below. If you have
a <Anchor href={nip07} external>compatible browser extension</Anchor> installed,
we will use that.
</p>
<div class="flex flex-col gap-4 items-center">
<div class="flex gap-4">
<Anchor class="w-32 text-center" type="button-accent" on:click={autoLogIn}>Log In</Anchor>
<Anchor class="w-32 text-center" type="button" on:click={signUp}>Sign Up</Anchor>
<div in:fly={{y: 20}}>
<Content size="lg" class="text-center">
<div class="flex flex-col gap-8 max-w-2xl">
<div class="flex justify-center items-center flex-col mb-4">
<Heading>Welcome!</Heading>
<i>To the Nostr Protocol</i>
</div>
<p class="text-center">
To log in to existing account, simply click below. If you have
a <Anchor href={nip07} external>compatible browser extension</Anchor> installed,
we will use that.
</p>
<div class="flex flex-col gap-4 items-center">
<div class="flex gap-4">
<Anchor class="w-32 text-center" type="button-accent" on:click={autoLogIn}>Log In</Anchor>
<Anchor class="w-32 text-center" type="button" on:click={signUp}>Sign Up</Anchor>
</div>
<Anchor type="unstyled" on:click={pubkeyLogIn}>
<i class="fa fa-cogs" /> Advanced Login
</Anchor>
</div>
<Anchor type="unstyled" on:click={pubkeyLogIn}>
<i class="fa fa-cogs" /> Advanced Login
</Anchor>
</div>
</div>
</Content>
</div>

View File

@ -1,6 +1,7 @@
<script>
import {fly} from 'svelte/transition'
import Anchor from 'src/partials/Anchor.svelte'
import Content from "src/partials/Content.svelte"
import {db} from 'src/agent'
let confirmed = false
@ -19,7 +20,7 @@
}
</script>
<div class="max-w-xl m-auto text-center py-20">
<Content size="lg" class="text-center">
{#if confirmed}
<div in:fly={{y:20}}>Clearing your local database...</div>
{:else}
@ -28,4 +29,4 @@
<Anchor type="button" on:click={confirm}>Log out</Anchor>
</div>
{/if}
</div>
</Content>

View File

@ -4,6 +4,8 @@
import {navigate} from "svelte-routing"
import Button from "src/partials/Button.svelte"
import Compose from "src/partials/Compose.svelte"
import Content from "src/partials/Content.svelte"
import Heading from 'src/partials/Heading.svelte'
import {user, getRelays} from "src/agent"
import {toast} from "src/app"
import cmd from "src/app/cmd"
@ -29,22 +31,18 @@
})
</script>
<div class="m-auto">
<form on:submit|preventDefault={onSubmit} class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
<div class="flex flex-col gap-4 max-w-lg w-full">
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">Create a note</h1>
</div>
<div class="flex flex-col gap-4 w-full">
<div class="flex flex-col gap-2">
<strong>What do you want to say?</strong>
<div class="border-l-2 border-solid border-medium pl-4">
<Compose bind:this={input} {onSubmit} />
</div>
<form on:submit|preventDefault={onSubmit} in:fly={{y: 20}}>
<Content size="lg">
<Heading class="text-center">Create a note</Heading>
<div class="flex flex-col gap-4 w-full">
<div class="flex flex-col gap-2">
<strong>What do you want to say?</strong>
<div class="border-l-2 border-solid border-medium pl-4">
<Compose bind:this={input} {onSubmit} />
</div>
<Button type="submit" class="text-center">Send</Button>
</div>
<Button type="submit" class="text-center">Send</Button>
</div>
</form>
</div>
</Content>
</form>

View File

@ -1,6 +1,7 @@
<script>
import {navigate} from 'svelte-routing'
import Anchor from "src/partials/Anchor.svelte"
import Content from "src/partials/Content.svelte"
import Tabs from "src/partials/Tabs.svelte"
import Network from "src/views/notes/Network.svelte"
import Latest from "src/views/notes/Latest.svelte"
@ -12,11 +13,11 @@
</script>
{#if !$user}
<div class="flex w-full justify-center items-center py-16">
<div class="text-center max-w-sm">
<Content size="lg" class="text-center">
<p>
Don't have an account? Click <Anchor href="/login">here</Anchor> to join the nostr network.
</div>
</div>
</p>
</Content>
{/if}
<Tabs tabs={['latest', 'network']} {activeTab} {setActiveTab} />

View File

@ -8,6 +8,7 @@
import {renderContent} from 'src/util/html'
import {displayPerson} from 'src/util/nostr'
import Tabs from "src/partials/Tabs.svelte"
import Content from "src/partials/Content.svelte"
import Button from "src/partials/Button.svelte"
import Spinner from "src/partials/Spinner.svelte"
import Notes from "src/views/person/Notes.svelte"
@ -23,7 +24,7 @@
export let activeTab
export let relays = null
const {privkey} = keys
const {canSign} = keys
let subs = []
let pubkey = nip19.decode(npub).data
@ -88,7 +89,8 @@
background-image:
linear-gradient(to bottom, rgba(0, 0, 0, 0.3), #0f0f0e),
url('{person.banner}')" />
<div class="max-w-xl m-auto flex flex-col gap-4 py-8 px-4">
<Content>
<div class="flex flex-col gap-4" in:fly={{y: 20}}>
<div class="flex gap-4">
<div
@ -104,11 +106,11 @@
<p>{@html renderContent(person.about || '')}</p>
</div>
<div class="whitespace-nowrap">
{#if $user?.pubkey === pubkey && $privkey}
{#if $user?.pubkey === pubkey && $canSign}
<a href="/profile" class="cursor-pointer text-sm">
<i class="fa-solid fa-edit" /> Edit
</a>
{:else if $user?.petnames && $privkey}
{:else if $user?.petnames && $canSign}
<div class="flex flex-col items-end gap-2">
{#if following}
<Button on:click={unfollow}>Unfollow</Button>
@ -120,27 +122,29 @@
</div>
</div>
</div>
{#if person?.petnames}
<div class="flex gap-8 ml-16">
<div><strong>{person.petnames.length}</strong> following</div>
<div><strong>{followersCount}</strong> followers</div>
</div>
{/if}
</div>
<Tabs tabs={['notes', 'likes', 'network']} {activeTab} {setActiveTab} />
{#if activeTab === 'notes'}
<Notes {pubkey} />
{:else if activeTab === 'likes'}
<Likes {pubkey} />
{:else if activeTab === 'network'}
{#if person?.petnames}
<Network person={person} />
{:else if loading}
<Spinner />
{:else}
<div class="py-16 max-w-xl m-auto flex justify-center">
Unable to show network for this person.
</div>
{/if}
{/if}
<Tabs tabs={['notes', 'likes', 'network']} {activeTab} {setActiveTab} />
{#if activeTab === 'notes'}
<Notes {pubkey} />
{:else if activeTab === 'likes'}
<Likes {pubkey} />
{:else if activeTab === 'network'}
{#if person?.petnames}
<Network person={person} />
{:else if loading}
<Spinner />
{:else}
<Content size="lg" class="text-center">
Unable to show network for this person.
</Content>
{/if}
{/if}
</Content>

View File

@ -8,6 +8,8 @@
import Textarea from "src/partials/Textarea.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Button from "src/partials/Button.svelte"
import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte"
import {user, getRelays} from "src/agent"
import {toast} from "src/app"
import {routes} from "src/app/ui"
@ -47,10 +49,10 @@
}
</script>
<form on:submit={submit} class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
<div class="flex flex-col gap-4 max-w-2xl">
<form on:submit={submit} in:fly={{y: 20}}>
<Content>
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">About You</h1>
<Heading>About You</Heading>
<p>
Give people a friendly way to recognize you. We recommend you do not use your real name or
share your personal information. The future of the internet is
@ -90,5 +92,5 @@
</div>
<Button type="submit" class="text-center">Done</Button>
</div>
</div>
</Content>
</form>

View File

@ -9,6 +9,7 @@
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Toggle from "src/partials/Toggle.svelte"
import Content from "src/partials/Content.svelte"
import {pool, db, user, ready} from "src/agent"
import {modal, addRelay, removeRelay, setRelayWriteCondition, settings} from "src/app"
import defaults from "src/agent/defaults"
@ -66,7 +67,7 @@
})
</script>
<div class="flex flex-col gap-6 m-auto max-w-2xl py-12">
<Content>
<div class="flex justify-between">
<div class="flex gap-2 items-center">
<i class="fa fa-server fa-lg" />
@ -161,4 +162,4 @@
of {($knownRelays || []).length - relays.length} known relays
</small>
</div>
</div>
</Content>

View File

@ -1,35 +1,26 @@
<script>
import {fly} from 'svelte/transition'
import Input from "src/partials/Input.svelte"
import Content from 'src/partials/Content.svelte'
import Tabs from 'src/partials/Tabs.svelte'
import SearchPeople from 'src/views/SearchPeople.svelte'
import SearchNotes from 'src/views/SearchNotes.svelte'
export let type
export let activeTab
let q = ''
const setActiveTab = tab => navigate(`/search/${tab}`)
</script>
<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 === 'people'}>
<a class="block px-8 py-4 " href="/search/people">People</a>
</li>
<li
class="cursor-pointer hover:border-b border-solid border-medium"
class:border-b={type === 'notes'}>
<a class="block px-8 py-4 " href="/search/notes">Notes</a>
</li>
</ul>
<div class="max-w-xl m-auto mt-4" in:fly={{y: 20}}>
<Content>
<Tabs tabs={['people', 'notes']} {activeTab} {setActiveTab} />
<Input bind:value={q} placeholder="Search for people">
<i slot="before" class="fa-solid fa-search" />
</Input>
</div>
{#if type === 'people'}
<SearchPeople {q} />
{:else if type === 'notes'}
<SearchNotes {q} />
{/if}
{#if activeTab === 'people'}
<SearchPeople {q} />
{:else if activeTab === 'notes'}
<SearchNotes {q} />
{/if}
</Content>

View File

@ -5,6 +5,8 @@
import Toggle from "src/partials/Toggle.svelte"
import Input from "src/partials/Input.svelte"
import Button from "src/partials/Button.svelte"
import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte"
import {user} from 'src/agent'
import {toast, settings} from "src/app"
@ -25,10 +27,10 @@
}
</script>
<form on:submit={submit} class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
<div class="flex flex-col gap-4 max-w-2xl">
<form on:submit={submit} in:fly={{y: 20}}>
<Content>
<div class="flex justify-center items-center flex-col mb-4">
<h1 class="staatliches text-6xl">App Settings</h1>
<Heading>App Settings</Heading>
<p>
Tweak Coracle to work the way you want it to.
</p>
@ -54,5 +56,5 @@
</div>
<Button type="submit" class="text-center">Save</Button>
</div>
</div>
</Content>
</form>

View File

@ -37,11 +37,9 @@ export class Tags {
}
}
export const getTagValues = tags => tags.map(t => t[1])
// Support the deprecated version where tags are not marked as replies
export const findReply = e =>
Tags.from(e).type("e").mark("reply").first() || Tags.from(e).type("e").first()
Tags.from(e).type("e").mark("reply").first() || Tags.from(e).type("e").last()
export const findReplyId = e => Tags.wrap([findReply(e)]).values().first()

View File

@ -1,29 +1,42 @@
<script>
import {nip19} from 'nostr-tools'
import {fly} from 'svelte/transition'
import {loadNote} from 'src/app'
import Note from 'src/partials/Note.svelte'
import Content from 'src/partials/Content.svelte'
import Spinner from 'src/partials/Spinner.svelte'
export let note
export let relays
if (!note.pubkey) {
loadNote(relays, note.id).then(found => {
note = found
})
} else {
console.log('NoteDetail', note)
let loading = true
const logNote = () => {
if (note) {
console.log('NoteDetail', nip19.noteEncode(note.id), note)
}
}
loadNote(relays, note.id).then(found => {
note = found
loading = false
logNote()
})
</script>
{#if !note}
<div class="p-4 text-center text-white" in:fly={{y: 20}}>
Sorry, we weren't able to find this note.
<div in:fly={{y: 20}}>
<Content size="lg" class="text-center">
Sorry, we weren't able to find this note.
</Content>
</div>
{:else if note.pubkey}
<div in:fly={{y: 20}}>
<Note invertColors anchorId={note.id} note={note} depth={2} />
</div>
{:else}
{/if}
{#if loading}
<Spinner />
{/if}

View File

@ -3,6 +3,7 @@
import {switcher, first} from 'hurdak/lib/hurdak'
import {fly} from 'svelte/transition'
import Button from "src/partials/Button.svelte"
import Content from 'src/partials/Content.svelte'
import SelectButton from "src/partials/SelectButton.svelte"
import {user, getRelays} from 'src/agent'
import {modal} from 'src/app'
@ -32,20 +33,22 @@
}
</script>
<form class="flex flex-col gap-4 w-full text-white max-w-2xl m-auto" in:fly={{y: 20}} on:submit={save}>
<div class="flex flex-col gap-2">
<h1 class="text-3xl">Advanced Follow</h1>
<p>
Fine grained controls for interacting with other people.
</p>
</div>
<div class="flex flex-col gap-1">
<strong>How often do you want to see notes from this person?</strong>
<SelectButton bind:value={values.muffle} options={muffleOptions} />
<p class="text-sm text-light">
"Never" is effectively a mute, while "Always" will show posts whenever available.
If you want a middle ground, choose "Sometimes" or "Often".
</p>
</div>
<Button type="submit" class="text-center">Done</Button>
<form in:fly={{y: 20}} on:submit={save}>
<Content class="text-white">
<div class="flex flex-col gap-2">
<h1 class="text-3xl">Advanced Follow</h1>
<p>
Fine grained controls for interacting with other people.
</p>
</div>
<div class="flex flex-col gap-1">
<strong>How often do you want to see notes from this person?</strong>
<SelectButton bind:value={values.muffle} options={muffleOptions} />
<p class="text-sm text-light">
"Never" is effectively a mute, while "Always" will show posts whenever available.
If you want a middle ground, choose "Sometimes" or "Often".
</p>
</div>
<Button type="submit" class="text-center">Done</Button>
</Content>
</form>

View File

@ -2,6 +2,8 @@
import {nip19} from 'nostr-tools'
import Input from 'src/partials/Input.svelte'
import Anchor from 'src/partials/Anchor.svelte'
import Content from 'src/partials/Content.svelte'
import Heading from 'src/partials/Heading.svelte'
import {toast, login} from "src/app"
let nsec = ''
@ -19,8 +21,8 @@
</script>
<div class="flex flex-col gap-8 text-white p-12">
<h1 class="staatliches text-4xl text-center">Login with your Private Key</h1>
<Content size="lg" class="text-center">
<Heading>Login with your Private Key</Heading>
<p>
To give Coracle full access to your nostr identity, enter your private key below.
</p>
@ -36,5 +38,5 @@
Note that sharing your private key directly is not recommended, instead you should use
a <Anchor href={nip07} external>compatible browser extension</Anchor> to securely store your key.
</p>
</div>
</Content>

View File

@ -2,6 +2,8 @@
import {nip19} from 'nostr-tools'
import Input from 'src/partials/Input.svelte'
import Anchor from 'src/partials/Anchor.svelte'
import Content from 'src/partials/Content.svelte'
import Heading from 'src/partials/Heading.svelte'
import {toast, login} from "src/app"
let npub = ''
@ -18,8 +20,8 @@
</script>
<div class="flex flex-col gap-8 text-white p-12">
<h1 class="staatliches text-4xl text-center">Login with your Public Key</h1>
<Content size="lg" class="text-center">
<Heading>Login with your Public Key</Heading>
<p>
For read-only access, enter your public key (or someone else's) below. Your
key should start with "npub".
@ -32,5 +34,5 @@
</div>
<Anchor type="button" on:click={logIn}>Log In</Anchor>
</div>
</div>
</Content>

View File

@ -10,26 +10,24 @@
export let q
let search = fuzzy(
let search
$: search = fuzzy(
Object.values($people).filter(prop('name')),
{keys: ["name", "about", "pubkey"]}
)
</script>
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
{#each search(q).slice(0, 30) as p (p.pubkey)}
{#if p.pubkey !== $user.pubkey}
<li in:fly={{y: 20}}>
<a href={routes.person(p.pubkey)} class="flex gap-4 my-4">
<div
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({p.picture})" />
<div class="flex-grow">
<h1 class="text-2xl">{displayPerson(p)}</h1>
<p>{@html renderContent(ellipsize(p.about || '', 140))}</p>
</div>
</a>
<li>
{/if}
{/each}
</ul>
{#each search(q).slice(0, 30) as p (p.pubkey)}
{#if p.pubkey !== $user.pubkey}
<a href={routes.person(p.pubkey)} class="flex gap-4">
<div
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({p.picture})" />
<div class="flex-grow">
<h1 class="text-2xl">{displayPerson(p)}</h1>
<p>{@html renderContent(ellipsize(p.about || '', 140))}</p>
</div>
</a>
{/if}
{/each}

View File

@ -3,6 +3,8 @@
import {copyToClipboard} from "src/util/html"
import Input from 'src/partials/Input.svelte'
import Anchor from 'src/partials/Anchor.svelte'
import Content from 'src/partials/Content.svelte'
import Heading from 'src/partials/Heading.svelte'
import {toast, login} from "src/app"
const nsec = nip19.nsecEncode(generatePrivateKey())
@ -19,8 +21,8 @@
</script>
<div class="flex flex-col gap-8 text-white p-12">
<h1 class="staatliches text-4xl text-center">Create an Account</h1>
<Content size="lg" class="text-center">
<Heading>Create an Account</Heading>
<p>
Don't have a nostr account? We've created a brand new private key for you below.
Make sure to click to copy and store it somewhere safe - this is your account's password!
@ -38,5 +40,5 @@
Note that sharing your private key directly is not recommended, instead you should use
a <Anchor href={nip07} external>compatible browser extension</Anchor> to securely store your key.
</p>
</div>
</Content>