mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add Content and Heading components
This commit is contained in:
parent
aaaef18cae
commit
c781e88574
12
README.md
12
README.md
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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) => {
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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}) => {
|
||||
|
@ -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)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
23
src/partials/Content.svelte
Normal file
23
src/partials/Content.svelte
Normal 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}
|
5
src/partials/Heading.svelte
Normal file
5
src/partials/Heading.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import cx from 'classnames'
|
||||
</script>
|
||||
|
||||
<h1 {...$$props} class={cx($$props.class, "staatliches text-6xl my-4")}><slot /></h1>
|
@ -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 />
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user