Fixing some bugs

This commit is contained in:
Jonathan Staab 2023-08-01 17:47:36 -07:00
parent 5c4432e6a4
commit e9c4ba44fe
28 changed files with 117 additions and 123 deletions

2
.env
View File

@ -1,6 +1,6 @@
VITE_THEME=transparent:transparent,black:#0f0f0e,white:#FFFFFF,accent:#EB5E28,accent-light:#FB652C,gray-dark:#8D8581,gray-light:#A8A5A4,danger:#ff0000,warning:#ebd112,success:#37ab51,input:#FAF6F1,input-hover:#F2EBE1
VITE_DEFAULT_FOLLOWS=1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110,e56e7b4326618f3d626c0e398f5082c3b16732e469e0a048b7ddb544c2be294a,011c1b374c12fbd3633e98957d3c46bed67983abecef50706c73a77c171d0d2c,b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc,b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,0fecf65daa26faf3f668e8143325a4c199a040b6345ed40a08614d7dd85b1823,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322
VITE_IMGPROXY_URL=https://imgproxy-cbpd.onrender.com
VITE_IMGPROXY_URL=https://imgproxy.coracle.social
VITE_DUFFLEPUD_URL=https://dufflepud.onrender.com
VITE_ENABLE_ZAPS=true
VITE_FORCE_RELAYS=

View File

@ -1,5 +1,7 @@
# Current
- [ ] Self profile detail in drop down
- [ ] close menu on click
- [ ] Fork and white label blowater
- [ ] Add relayset support with kind 30022
- [ ] White-labeled

View File

@ -103,12 +103,11 @@
}
}
onMount(() => {
let scrollY
let scrollY
onMount(() => {
// Log modals, keep scroll position on body, but don't allow scrolling
const unsubModal = modal.stack.subscribe($stack => {
console.log("=======", $stack, scrollY)
if (find(x => !x.mini, $stack)) {
logUsage(btoa(["modal", last($stack).type].join(":")))

View File

@ -8,7 +8,6 @@
let scrollY = 0
$: showCreateNote = !$location.pathname.match(/messages.|chat.|relays.|keys|settings|logout$/)
$: showLogin = !$location.pathname.match(/login$/)
const {canSign} = Keys
@ -44,13 +43,4 @@
<i class="fa fa-plus" />
</button>
{/if}
{#if !$canSign && showLogin}
<button
class="color-white flex h-16 w-16 items-center justify-center rounded-full
border border-accent-light bg-accent text-white shadow-2xl
transition-all hover:scale-105 hover:bg-accent-light"
on:click={() => navigate("/login")}>
<i class="fa fa-right-to-bracket" />
</button>
{/if}
</div>

View File

@ -3,6 +3,7 @@
import Content from "src/partials/Content.svelte"
import Spinner from "src/partials/Spinner.svelte"
import ChatEdit from "src/app/views/ChatEdit.svelte"
import Login from "src/app/views/Login.svelte"
import LoginConnect from "src/app/views/LoginConnect.svelte"
import LoginPrivKey from "src/app/views/LoginPrivKey.svelte"
import LoginAdvanced from "src/app/views/LoginAdvanced.svelte"
@ -54,6 +55,8 @@
<Onboarding stage={m.stage} />
{:else if m.type === "channel/edit"}
<ChatEdit {...m} />
{:else if m.type === "login/intro"}
<Login />
{:else if m.type === "login/privkey"}
<LoginPrivKey />
{:else if m.type === "login/advanced"}

View File

@ -31,7 +31,7 @@
</script>
{#if ready}
<div class="pt-16 text-gray-2 lg:ml-56">
<div class="pt-16 text-gray-2 lg:ml-48">
<TypedRoute path="/notifications" component={Notifications} />
<TypedRoute path="/notifications/:activeTab" component={Notifications} />
<TypedRoute path="/notes" let:params>
@ -69,7 +69,6 @@
</TypedRoute>
<TypedRoute path="/profile" component={UserProfile} />
<TypedRoute path="/settings" component={UserSettings} />
<TypedRoute path="/login" component={Login} />
<TypedRoute path="/logout" component={Logout} />
<TypedRoute path="/:entity" let:params>
{#key params.entity}

View File

@ -28,7 +28,7 @@
<ul
class="fixed bottom-0 left-0 top-0 z-20 mt-16 w-48 overflow-hidden border-r border-gray-6 bg-gray-7 pb-20
pt-4 text-gray-2 shadow-xl transition-all lg:ml-0"
class:-ml-56={!$menuIsOpen}>
class:-ml-48={!$menuIsOpen}>
<li class="cursor-pointer">
<a class="block px-4 py-2 transition-all hover:bg-accent hover:text-white" href="/notes">
<i class="fa fa-rss mr-2" /> Feed

View File

@ -34,6 +34,8 @@
let scanner
let searchInput
const showLogin = () => modal.push({type: 'login/intro'})
const showSearch = () => {
term = ""
searchInput.focus()
@ -173,7 +175,7 @@
<div slot="trigger" class="relative flex cursor-pointer items-center">
<PersonCircle size={10} pubkey={$pubkey} />
{#if $hasNewNotfications}
<div class="absolute right-0 h-[9px] w-[9px] rounded bg-accent" />
<div class="absolute right-0 top-0 h-[9px] w-[9px] rounded bg-accent" />
{/if}
</div>
<div slot="tooltip" class="flex justify-end">
@ -217,16 +219,19 @@
</div>
</Popover>
{:else}
<Anchor theme="button-primary" href="/login">Log In</Anchor>
<Anchor theme="button-accent" on:click={showLogin}>Log In</Anchor>
{/if}
</div>
<div
class={cx(
"search-input pointer-events-none fixed top-0 z-10 w-full px-2 text-white sm:pr-16",
"search-input pointer-events-none fixed top-0 z-10 w-full px-2 text-gray-1",
"flex h-16 items-center justify-end gap-4",
{
"pr-16": term === null,
"sm:pr-16": $pubkey,
"sm:pr-28": !$pubkey,
"pr-16": term === null && $pubkey,
"pr-28": term === null && !$pubkey,
"z-40 pr-0": term,
}
)}>
@ -247,7 +252,7 @@
"pointer-events-auto cursor-pointer text-black transition-all",
{
"-mr-6 w-0 opacity-0": term === null,
"opacity-1 -ml-12 sm:-mr-1 sm:w-64 w-full pl-10": term !== null,
"opacity-1 -ml-12 w-full pl-10 sm:-mr-1 sm:w-64": term !== null,
}
)} />
<div

View File

@ -22,7 +22,7 @@
<NoteContentKind0 {note} />
{:else if note.kind === 3}
<NoteContentKind3 {note} {showEntire} />
{:else if note.kind === 40}
{:else if [40, 41].includes(note.kind)}
<NoteContentKind40 {note} />
{:else if note.kind === 1985}
<NoteContentKind1985 {note} {anchorId} {maxLength} {showEntire} />

View File

@ -3,6 +3,7 @@
import {navigate} from "svelte-routing"
import {nip19} from "nostr-tools"
import {tryJson} from "src/util/misc"
import {Tags} from "src/util/nostr"
import Card from "src/partials/Card.svelte"
import Content from "src/partials/Content.svelte"
import ImageCircle from "src/partials/ImageCircle.svelte"
@ -14,7 +15,7 @@
const channel = Nip28.channels
.key(note.id)
.derived(defaultTo({id: note.id, name, picture, about}))
const noteId = nip19.noteEncode(note.id)
const noteId = nip19.noteEncode(note.kind === 40 ? note.id : Tags.from(note).getMeta("e"))
</script>
<Card interactive invertColors on:click={() => navigate(`/chat/${noteId}`)}>

View File

@ -101,7 +101,7 @@
<div class="flex gap-2 rounded-b bg-gray-7 p-2 text-sm text-gray-2">
<div class="inline-block border-r border-solid border-gray-6 py-2 pl-1 pr-3">
<div class="flex cursor-pointer items-center gap-3">
<ImageInput bind:value={data.image} icon="image" hideInput>
<ImageInput bind:value={data.image}>
<i slot="button" class="fa fa-paperclip" />
</ImageInput>
<i class="fa fa-at" />

View File

@ -66,7 +66,7 @@
</script>
<div class="flex items-center gap-3">
{#if canSign && !isSelf}
{#if !isSelf}
<Popover triggerType="mouseenter">
<div slot="trigger" class="w-6 text-center">
{#if $muted}

View File

@ -24,7 +24,9 @@
</div>
</Anchor>
{#if !hideActions}
<PersonActions {pubkey} />
<slot name="actions">
<PersonActions {pubkey} />
</slot>
{/if}
</div>
<PersonAbout truncate {pubkey} />

View File

@ -1,11 +1,9 @@
<script lang="ts">
import {onMount} from "svelte"
import {fly} from "src/util/transition"
import {error} from "src/util/logger"
import {stripExifData} from "src/util/html"
import Input from "src/partials/Input.svelte"
import Content from "src/partials/Content.svelte"
import Textarea from "src/partials/Textarea.svelte"
import ImageInput from "src/partials/ImageInput.svelte"
import Anchor from "src/partials/Anchor.svelte"
import {toast, modal} from "src/partials/state"
import {Builder, user} from "src/app/engine"
@ -13,22 +11,6 @@
export let channel = {name: null, id: null, about: null, picture: null}
onMount(async () => {
document.querySelector("[name=picture]").addEventListener("change", async e => {
const target = e.target as HTMLInputElement
const [file] = target.files
if (file) {
const reader = new FileReader()
reader.onerror = error
reader.onload = () => (channel.picture = reader.result)
reader.readAsDataURL(await stripExifData(file))
} else {
channel.picture = null
}
})
})
const submit = async e => {
e.preventDefault()
@ -73,7 +55,7 @@
</div>
<div class="flex flex-col gap-1">
<strong>Picture</strong>
<input type="file" name="picture" />
<ImageInput icon="image" bind:value={channel.picture} />
<p class="text-sm text-gray-1">A picture to help people remember your room.</p>
</div>
<Anchor tag="button" theme="button" type="submit" class="text-center">Done</Anchor>

View File

@ -3,17 +3,24 @@
import {debounce} from "throttle-debounce"
import {batch, seconds} from "hurdak"
import {complement, propEq, sortBy, pipe, pluck, filter, uniq, prop} from "ramda"
import {now} from "src/util/misc"
import {now, createScroller} from "src/util/misc"
import {Tags} from "src/util/nostr"
import {modal} from "src/partials/state"
import Input from "src/partials/Input.svelte"
import Content from "src/partials/Content.svelte"
import Anchor from "src/partials/Anchor.svelte"
import ChatListItem from "src/app/views/ChatListItem.svelte"
import {pubkeyLoader, Nip28, Nip65, Network, Keys} from "src/app/engine"
import {pubkeyLoader, Nip28, Nip65, Network, Keys, user} from "src/app/engine"
let q = ""
let results = []
let limit = 5
const loadMore = () => {
limit += 5
}
const scroller = createScroller(loadMore)
const channels = Nip28.channels.derived(
pipe(
@ -21,6 +28,7 @@
sortBy(c => -(c.last_sent || c.last_received))
)
)
const joined = channels.derived(filter(prop("joined")))
const other = channels.derived(filter(complement(prop("joined"))))
const search = Nip28.getSearchChannels(other)
@ -36,7 +44,7 @@
})
$: searchChannels(q)
$: results = $search(q).slice(0, 50)
$: results = $search(q).slice(0, limit)
document.title = "Chat"
@ -51,7 +59,13 @@
timeout: 2000,
filter: [
{kinds: [40, 41], authors: [Keys.pubkey.get()]},
{kinds: [42], since: now() - seconds(1, "day"), limit: 100},
{kinds: [40, 41], authors: user.getFollows().slice(0, 256), limit: 100},
{
limit: 100,
kinds: [42],
since: now() - seconds(1, "day"),
authors: user.getFollows().slice(0, 256),
},
],
onEvent: batch(500, events => {
const channelIds = uniq(
@ -76,6 +90,7 @@
return () => {
subs.map(s => s.close())
scroller.stop()
}
})
</script>
@ -107,14 +122,9 @@
<i slot="before" class="fa-solid fa-search" />
</Input>
</div>
{#if results.length > 0}
{#each results as channel (channel.id)}
<ChatListItem {channel} />
{/each}
<small class="text-center">
Showing {Math.min(50, $other.length)} of {$other.length} known rooms
</small>
{#each results as channel (channel.id)}
<ChatListItem {channel} />
{:else}
<small class="text-center"> No matching rooms found </small>
{/if}
{/each}
</Content>

View File

@ -11,7 +11,12 @@
const enter = () => navigate(`/chat/${nip19.noteEncode(channel.id)}`)
const join = () => user.joinChannel(channel.id)
const leave = () => user.leaveChannel(channel.id)
const picture = Settings.imgproxy(channel.picture, {w: 112, h: 112})
// Accommodate data urls from legacy
const picture =
channel.picture?.length > 500
? channel.picture
: Settings.imgproxy(channel.picture, {w: 112, h: 112})
</script>
<button

View File

@ -1,7 +1,6 @@
<script lang="ts">
import cx from "classnames"
import type {DynamicFilter} from "src/engine/types"
import {objOf} from "ramda"
import {Tags, noteKinds} from "src/util/nostr"
import {modal, theme} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte"
@ -19,9 +18,9 @@
authors: user.getFollows().length > 0 ? "follows" : "network",
} as DynamicFilter
const showLists = () => {
modal.push({type: "list/list"})
}
const showLists = () => modal.push({type: "list/list"})
const showLogin = () => modal.push({type: 'login/intro'})
const loadListFeed = naddr => {
const list = engine.Content.lists.key(naddr).get()
@ -30,7 +29,7 @@
const urls = Tags.wrap(list.tags).urls()
if (urls.length > 0) {
relays = urls.map(objOf("url"))
relays = urls
}
filter = {kinds: [1, 1985], authors: "global"} as DynamicFilter
@ -54,7 +53,7 @@
<Content size="lg" class="text-center">
<p class="text-xl">Don't have an account?</p>
<p>
Click <Anchor class="underline" href="/login">here</Anchor> to join the nostr network.
Click <Anchor class="underline" on:click={showLogin}>here</Anchor> to join the nostr network.
</p>
</Content>
{/if}

View File

@ -15,7 +15,7 @@
// Give them a moment to see the state transition. IndexedDB
// also apparently needs some time
setTimeout(() => {
window.location.href = "/login"
window.location.href = "/notes"
}, 1000)
}

View File

@ -140,7 +140,7 @@
<div class="flex gap-2">
<Anchor tag="button" theme="button" type="submit" class="flex-grow text-center"
>Send</Anchor>
<ImageInput multi onChange={addImage} icon="image" hideInput />
<ImageInput multi onChange={addImage} />
</div>
<small
class="flex cursor-pointer items-center justify-end gap-1"

View File

@ -1,5 +1,5 @@
<script lang="ts">
import {reject} from "ramda"
import {reject, prop} from "ramda"
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
@ -10,15 +10,15 @@
import {modal} from "src/partials/state"
const {searchProfiles} = Directory
const follows = Nip02.graph.derived(() => user.getFollowsSet())
const follows = Nip02.graph.key(user.getStateKey()).derived(g => g ? g.petnames.map(t => t[1]) : [])
if ($follows.size === 0) {
if ($follows.length === 0) {
user.setPetnames(Env.DEFAULT_FOLLOWS.map(Builder.mention))
}
let q = ""
$: results = reject((p: Profile) => $follows.has(p.pubkey), $searchProfiles(q))
$: results = reject((p: Profile) => $follows.includes(p.pubkey), $searchProfiles(q))
</script>
<Content>
@ -38,14 +38,20 @@
<i class="fa fa-user-astronaut fa-lg" />
<h2 class="staatliches text-2xl">Your follows</h2>
</div>
{#if $follows.size === 0}
{#if $follows.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No follows selected</span>
</div>
{:else}
{#each Array.from($follows) as pubkey}
<PersonSummary {pubkey} />
{#each $follows as pubkey (pubkey)}
<PersonSummary {pubkey}>
<div slot="actions">
<Anchor theme="button" class="flex items-center gap-2" on:click={() => user.unfollow(pubkey)}>
<i class="fa fa-user-slash" /> Unfollow
</Anchor>
</div>
</PersonSummary>
{/each}
{/if}
<div class="flex items-center gap-2">
@ -56,6 +62,12 @@
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results.slice(0, 50) as profile (profile.pubkey)}
<PersonSummary pubkey={profile.pubkey} />
<PersonSummary pubkey={profile.pubkey}>
<div slot="actions">
<Anchor theme="button-accent" class="flex items-center gap-2" on:click={() => user.follow(profile.pubkey)}>
<i class="fa fa-user-plus" /> Follow
</Anchor>
</div>
</PersonSummary>
{/each}
</Content>

View File

@ -1,5 +1,4 @@
<script>
import {onMount} from "svelte"
import {fly} from "src/util/transition"
import {navigate} from "svelte-routing"
import {nip19} from "nostr-tools"
@ -26,12 +25,6 @@
toast.show("info", `Your ${type} key has been copied to the clipboard.`)
}
onMount(async () => {
if (!$pubkey) {
return navigate("/login")
}
})
document.title = "Keys"
</script>

View File

@ -1,5 +1,4 @@
<script lang="ts">
import {onMount} from "svelte"
import {fly} from "src/util/transition"
import {navigate} from "svelte-routing"
import Input from "src/partials/Input.svelte"
@ -17,12 +16,6 @@
const pseudUrl =
"https://www.coindesk.com/markets/2020/06/29/many-bitcoin-developers-are-choosing-to-use-pseudonyms-for-good-reason/"
onMount(async () => {
if (!Keys.canSign.get()) {
return navigate("/login")
}
})
const submit = async event => {
const relays = user.getRelayUrls("write")

View File

@ -3,16 +3,14 @@ import {nth, inc} from "ramda"
import {fuzzy} from "src/util/misc"
import {Tags} from "src/util/nostr"
import type {Topic, List} from "src/engine/types"
import {derived, collection} from "src/engine/util/store"
import {collection} from "src/engine/util/store"
import type {Engine} from "src/engine/Engine"
import type {Event} from "src/engine/types"
export class Content {
topics = collection<Topic>("name")
lists = collection<List>("naddr")
searchTopics = derived(this.topics, $topics =>
fuzzy($topics.values(), {keys: ["name"], threshold: 0.3})
)
searchTopics = this.topics.derived($topics => fuzzy($topics, {keys: ["name"], threshold: 0.3}))
getLists = (f: (l: List) => boolean) =>
this.lists.get().filter(l => !l.deleted_at && (f ? f(l) : true))

View File

@ -1,4 +1,4 @@
import {partition, identity, uniqBy, pluck, sortBy, without, any, prop, assoc} from "ramda"
import {partition, uniqBy, pluck, sortBy, without, any, prop, assoc} from "ramda"
import {ensurePlural, seconds, doPipe, throttle, batch} from "hurdak"
import {now, race} from "src/util/misc"
import {findReplyId} from "src/util/nostr"
@ -106,9 +106,10 @@ export class FeedLoader {
addToFeed = (notes: Event[]) => {
this.feed.update($feed => {
// Avoid showing the same note twice, even if it's once as
// a parent and once as a child
const ids = new Set(pluck("id", $feed).concat($feed.map(findReplyId).filter(identity)))
// Avoid showing the same note twice, even if it's once as a parent and once as a child
const feedIds = new Set(pluck("id", $feed))
// Maybe this?
// const feedIds = new Set(pluck("id", $feed).concat($feed.concat(notes).map(findReplyId).filter(identity)))
return uniqBy(
prop("id"),
@ -116,7 +117,7 @@ export class FeedLoader {
this.context.applyContext(
sortBy(
e => -e.created_at,
notes.filter(e => !ids.has(e.id) && !ids.has(findReplyId(e)))
notes.filter(e => !feedIds.has(findReplyId(e)))
),
true
)

View File

@ -85,7 +85,7 @@ export class StorageAdapter {
if (window.indexedDB) {
const policies = [
policy("Alerts.events", 500, sortBy(prop("created_at"))),
policy("Nip28.channels", 1000, sortChannels),
policy("Nip28.channels", 2000, sortChannels),
policy("Nip28.messages", 10000, sortBy(prop("created_at"))),
policy("Nip04.contacts", 1000, sortContacts),
policy("Nip04.messages", 10000, sortBy(prop("created_at"))),

View File

@ -91,7 +91,7 @@
<div class="flex h-full gap-4">
<div class="relative w-full">
<div class="-mt-16 pt-20 flex h-screen flex-col" class:pb-20={Keys.canSign.get()}>
<div class="-mt-16 flex h-screen flex-col pt-20" class:pb-20={Keys.canSign.get()}>
<ul
class="channel-messages flex flex-grow flex-col-reverse justify-start overflow-auto p-4 pb-6">
{#each $groupedMessages as m (m.id)}
@ -111,7 +111,7 @@
</div>
{#if Keys.canSign.get()}
<div
class="fixed bottom-0 z-10 flex w-full border-t border-solid border-gray-6 border-gray-7 bg-gray-6 lg:-ml-56 lg:pl-56">
class="fixed bottom-0 z-10 flex w-full border-t border-solid border-gray-6 border-gray-7 bg-gray-6 lg:-ml-48 lg:pl-48">
<textarea
rows="3"
autofocus
@ -121,7 +121,7 @@
class="w-full resize-none bg-gray-6 p-2
text-gray-2 outline-0 placeholder:text-gray-1" />
<div>
<ImageInput hideInput onChange={addImage} icon="image">
<ImageInput onChange={addImage}>
<button
slot="button"
class="flex cursor-pointer flex-col justify-center gap-2 border-l border-solid border-gray-7 p-3

View File

@ -8,12 +8,11 @@
import {listenForFile, stripExifData, blobToFile} from "src/util/html"
import {Settings} from "src/app/engine"
export let icon
export let icon = null
export let value = null
export let multi = false
export let maxWidth = null
export let maxHeight = null
export let hideInput = false
export let onChange = null
let input, listener, quote
@ -70,23 +69,24 @@
}
</script>
{#if !hideInput}
<Input type="text" wrapperClass="flex-grow" bind:value placeholder="https://">
<i slot="before" class={`fa fa-${icon}`} />
</Input>
{/if}
<div
on:click={() => {
isOpen = true
}}>
<slot name="button">
<div class="flex">
<Anchor theme="button">
<i class="fa fa-upload" />
</Anchor>
</div>
</slot>
<div class="flex gap-2">
{#if icon}
<Input type="text" wrapperClass="flex-grow" bind:value placeholder="https://">
<i slot="before" class={`fa fa-${icon}`} />
</Input>
{/if}
<div
on:click={() => {
isOpen = true
}}>
<slot name="button">
<div class="flex">
<Anchor theme="button">
<i class="fa fa-upload" />
</Anchor>
</div>
</slot>
</div>
</div>
{#if quote}

View File

@ -171,7 +171,7 @@ export const mergeFilter = (filter: Filter | Filter[], extra: Filter) =>
export const fromNostrURI = (s: string) => s.replace(/^[\w\+]+:\/?\/?/, "")
export const toNostrURI = (s: string) => `web+nostr://${s}`
export const toNostrURI = (s: string) => `nostr://${s}`
export const getLabelQuality = (label: string, event: Event) => {
const json = tryJson(() => JSON.parse(last(Tags.from(event).type("l").equals(label).first())))