mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Add support for parsing and displaying lnurl invoices
This commit is contained in:
parent
7dd6edaac5
commit
a40b9268f6
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type {Filter} from "nostr-tools"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {Filter} from "nostr-tools"
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {last, equals, partition, always, uniqBy, sortBy, prop} from "ramda"
|
||||
import {fly} from "svelte/transition"
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {pluck} from 'ramda'
|
||||
import {Filter} from 'nostr-tools'
|
||||
import type {Filter} from "nostr-tools"
|
||||
import {pluck} from "ramda"
|
||||
import {fly} from "svelte/transition"
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {createLocalDate} from 'src/util/misc'
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {createLocalDate} from "src/util/misc"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
@ -20,29 +20,29 @@
|
||||
until: null,
|
||||
authors: [],
|
||||
search: "",
|
||||
'#t': [],
|
||||
'#p': [],
|
||||
"#t": [],
|
||||
"#p": [],
|
||||
}
|
||||
|
||||
let modal = null
|
||||
|
||||
const applyFilter = debounce(300, () => {
|
||||
if (modal !== 'maxi') {
|
||||
if (modal !== "maxi") {
|
||||
const _filter = {} as Filter
|
||||
|
||||
if (filter.since) _filter.since = createLocalDate(filter.since).setHours(23, 59, 59, 0) / 1000
|
||||
if (filter.until) _filter.until = createLocalDate(filter.until).setHours(23, 59, 59, 0) / 1000
|
||||
if (filter.authors.length > 0) _filter.authors = pluck('pubkey', filter.authors)
|
||||
if (filter.authors.length > 0) _filter.authors = pluck("pubkey", filter.authors)
|
||||
if (filter.search) _filter.search = filter.search
|
||||
if (filter['#t'].length > 0) _filter['#t'] = pluck('name', filter['#t'])
|
||||
if (filter['#p'].length > 0) _filter['#p'] = pluck('pubkey', filter['#p'])
|
||||
if (filter["#t"].length > 0) _filter["#t"] = pluck("name", filter["#t"])
|
||||
if (filter["#p"].length > 0) _filter["#p"] = pluck("pubkey", filter["#p"])
|
||||
|
||||
onChange(_filter)
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
modal = 'maxi'
|
||||
modal = "maxi"
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
@ -55,71 +55,75 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 justify-end" in:fly={{y: 20}}>
|
||||
<i class="fa fa-search cursor-pointer" on:click={() => {modal = modal ? null : 'mini'}} />
|
||||
<div class="flex justify-end gap-2" in:fly={{y: 20}}>
|
||||
<i
|
||||
class="fa fa-search cursor-pointer"
|
||||
on:click={() => {
|
||||
modal = modal ? null : "mini"
|
||||
}} />
|
||||
<i class="fa fa-sliders cursor-pointer" on:click={open} />
|
||||
</div>
|
||||
|
||||
{#if modal}
|
||||
<Modal {onEscape} mini={modal === 'mini'}>
|
||||
<Content size="lg">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Search</strong>
|
||||
<Input bind:value={filter.search} on:input={applyFilter}>
|
||||
<i slot="before" class="fa fa-search" />
|
||||
</Input>
|
||||
</div>
|
||||
{#if modal === 'maxi'}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Since</strong>
|
||||
<Input type="date" bind:value={filter.since} />
|
||||
<Modal {onEscape} mini={modal === "mini"}>
|
||||
<Content size="lg">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Search</strong>
|
||||
<Input bind:value={filter.search} on:input={applyFilter}>
|
||||
<i slot="before" class="fa fa-search" />
|
||||
</Input>
|
||||
</div>
|
||||
{#if modal === "maxi"}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Since</strong>
|
||||
<Input type="date" bind:value={filter.since} />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Until</strong>
|
||||
<Input type="date" bind:value={filter.until} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Until</strong>
|
||||
<Input type="date" bind:value={filter.until} />
|
||||
{#if !hide.includes("authors")}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Authors</strong>
|
||||
<MultiSelect search={$searchPeople} bind:value={filter.authors}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
<PersonBadge inert person={getPersonWithFallback(item.pubkey)} />
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !hide.includes("#t")}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Topics</strong>
|
||||
<MultiSelect search={$searchTopics} bind:value={filter["#t"]}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
#{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !hide.includes("#p")}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Mentions</strong>
|
||||
<MultiSelect search={$searchPeople} bind:value={filter["#p"]}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
<PersonBadge inert person={getPersonWithFallback(item.pubkey)} />
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-end">
|
||||
<Anchor type="button-accent" on:click={submit}>Apply Filters</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
{#if !hide.includes('authors')}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Authors</strong>
|
||||
<MultiSelect search={$searchPeople} bind:value={filter.authors}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
<PersonBadge inert person={getPersonWithFallback(item.pubkey)} />
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !hide.includes('#t')}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Topics</strong>
|
||||
<MultiSelect search={$searchTopics} bind:value={filter['#t']}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
#{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !hide.includes('#p')}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Mentions</strong>
|
||||
<MultiSelect search={$searchPeople} bind:value={filter['#p']}>
|
||||
<div slot="item" let:item>
|
||||
<div class="-my-1">
|
||||
<PersonBadge inert person={getPersonWithFallback(item.pubkey)} />
|
||||
</div>
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-end">
|
||||
<Anchor type="button-accent" on:click={submit}>Apply Filters</Anchor>
|
||||
</div>
|
||||
{/if}
|
||||
</Content>
|
||||
</Modal>
|
||||
</Content>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
@ -6,6 +6,7 @@
|
||||
import {displayPerson, parseContent, getLabelQuality, displayRelay, Tags} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
import MediaSet from "src/partials/MediaSet.svelte"
|
||||
import QRCode from "src/partials/QRCode.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
@ -29,15 +30,22 @@
|
||||
let rating = note.kind === 1985 ? getLabelQuality("review/relay", note) : null
|
||||
|
||||
const links = []
|
||||
const invoices = []
|
||||
const ranges = []
|
||||
|
||||
// Find links and preceding whitespace
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const {type, value} = content[i]
|
||||
|
||||
if (type === "link" && !value.startsWith("ws")) {
|
||||
if (type === "link") {
|
||||
links.push(value)
|
||||
}
|
||||
|
||||
if (type === "lnurl") {
|
||||
invoices.push(value)
|
||||
}
|
||||
|
||||
if (["link", "lnurl"].includes(type) && !value.startsWith("ws")) {
|
||||
const prev = content[i - 1]
|
||||
const next = content[i + 1]
|
||||
|
||||
@ -197,6 +205,11 @@
|
||||
{" "}
|
||||
{/each}
|
||||
</p>
|
||||
{#if invoices.length > 0}
|
||||
<div on:click|stopPropagation>
|
||||
<QRCode fullWidth onClick="copy" code={invoices[0]} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if showMedia && links.length > 0}
|
||||
<div on:click|stopPropagation>
|
||||
<MediaSet {links} />
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import QRCode from "qrcode"
|
||||
import {onMount} from "svelte"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
@ -7,6 +8,8 @@
|
||||
import {toast} from "src/partials/state"
|
||||
|
||||
export let code
|
||||
export let onClick = "navigate"
|
||||
export let fullWidth = false
|
||||
|
||||
let canvas
|
||||
|
||||
@ -21,11 +24,20 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="m-auto flex max-w-sm flex-col gap-4 rounded border border-solid border-gray-6 bg-gray-8 p-4">
|
||||
<Anchor external href={code}>
|
||||
<canvas class="m-auto rounded" bind:this={canvas} />
|
||||
</Anchor>
|
||||
<Input value={code}>
|
||||
<button slot="after" class="fa fa-copy" on:click={copy} />
|
||||
</Input>
|
||||
class={cx("rounded-xl border border-solid border-gray-6 bg-gray-8 p-4", {
|
||||
"m-auto max-w-sm": !fullWidth,
|
||||
})}>
|
||||
<div class="m-auto flex max-w-sm flex-col gap-4">
|
||||
<Anchor
|
||||
external
|
||||
href={onClick === "navigate" ? code : null}
|
||||
on:click={onClick === "copy" ? copy : null}>
|
||||
<canvas class="m-auto rounded-xl" bind:this={canvas} />
|
||||
</Anchor>
|
||||
{#if onClick === "navigate"}
|
||||
<Input value={code}>
|
||||
<button slot="after" class="fa fa-copy" on:click={copy} />
|
||||
</Input>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
const className = cx(
|
||||
$$props.class,
|
||||
"rounded shadow-inset py-2 px-4 pr-10 w-full bg-input text-black",
|
||||
"rounded-xl shadow-inset py-2 px-4 pr-10 w-full bg-input text-black",
|
||||
"placeholder:text-gray-5 border border-solid border-gray-3"
|
||||
)
|
||||
</script>
|
||||
|
@ -265,6 +265,14 @@ export const parseContent = ({content, tags = []}) => {
|
||||
}
|
||||
}
|
||||
|
||||
const parseLNUrl = () => {
|
||||
const lnurl = first(text.match(/^lnbc[\d\w]+/i))
|
||||
|
||||
if (lnurl) {
|
||||
return ["lnurl", lnurl, lnurl]
|
||||
}
|
||||
}
|
||||
|
||||
const parseUrl = () => {
|
||||
const raw = first(text.match(/^([a-z\+:]{2,30}:\/\/)?[^\s]+\.[a-z]{2,6}[^\s]*[^\.!?,:\s]/gi))
|
||||
|
||||
@ -292,7 +300,13 @@ export const parseContent = ({content, tags = []}) => {
|
||||
}
|
||||
|
||||
while (text) {
|
||||
const part = parseNewline() || parseMention() || parseTopic() || parseBech32() || parseUrl()
|
||||
const part =
|
||||
parseNewline() ||
|
||||
parseMention() ||
|
||||
parseTopic() ||
|
||||
parseBech32() ||
|
||||
parseUrl() ||
|
||||
parseLNUrl()
|
||||
|
||||
if (part) {
|
||||
if (buffer) {
|
||||
|
Loading…
Reference in New Issue
Block a user