Browse Source

Moved profile into a card, added lnurl and QR code

master
Nuša Pukšič 10 months ago
parent
commit
c201135d9a
  1. 218
      package-lock.json
  2. 5
      package.json
  3. 1
      src/app.css
  4. 76
      src/lib/components/EventDetails.svelte
  5. 2
      src/lib/components/Preview.svelte
  6. 2
      src/lib/components/Publication.svelte
  7. 0
      src/lib/components/cards/BlogHeader.svelte
  8. 113
      src/lib/components/cards/ProfileHeader.svelte
  9. 5
      src/lib/components/util/CopyToClipboard.svelte
  10. 17
      src/lib/components/util/QrCode.svelte
  11. 5
      src/styles/events.css

218
package-lock.json generated

@ -14,11 +14,13 @@
"@tailwindcss/forms": "0.5.x", "@tailwindcss/forms": "0.5.x",
"@tailwindcss/typography": "0.5.x", "@tailwindcss/typography": "0.5.x",
"asciidoctor": "3.0.x", "asciidoctor": "3.0.x",
"bech32": "^2.0.0",
"d3": "^7.9.0", "d3": "^7.9.0",
"he": "1.2.x", "he": "1.2.x",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"node-emoji": "^2.2.0", "node-emoji": "^2.2.0",
"nostr-tools": "2.10.x" "nostr-tools": "2.10.x",
"qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.50.1", "@playwright/test": "^1.50.1",
@ -30,6 +32,7 @@
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@types/he": "1.2.x", "@types/he": "1.2.x",
"@types/node": "22.x", "@types/node": "22.x",
"@types/qrcode": "^1.5.5",
"autoprefixer": "10.x", "autoprefixer": "10.x",
"eslint-plugin-svelte": "2.x", "eslint-plugin-svelte": "2.x",
"flowbite": "2.x", "flowbite": "2.x",
@ -1230,6 +1233,16 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/qrcode": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.20.2", "version": "1.20.2",
"dev": true, "dev": true,
@ -1561,6 +1574,12 @@
"version": "1.0.2", "version": "1.0.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bech32": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==",
"license": "MIT"
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"license": "MIT", "license": "MIT",
@ -1673,6 +1692,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase-css": { "node_modules/camelcase-css": {
"version": "2.0.1", "version": "2.0.1",
"license": "MIT", "license": "MIT",
@ -2236,6 +2264,15 @@
} }
} }
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/deep-eql": { "node_modules/deep-eql": {
"version": "5.0.2", "version": "5.0.2",
"dev": true, "dev": true,
@ -2278,6 +2315,12 @@
"version": "1.2.2", "version": "1.2.2",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"license": "MIT" "license": "MIT"
@ -3910,6 +3953,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
@ -3928,9 +3980,7 @@
}, },
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -4045,6 +4095,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.3", "version": "8.5.3",
"funding": [ "funding": [
@ -4381,6 +4440,141 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/qrcode/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/qrcode/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/qrcode/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"funding": [ "funding": [
@ -4437,6 +4631,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"license": "MIT", "license": "MIT",
@ -4565,6 +4765,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/set-cookie-parser": { "node_modules/set-cookie-parser": {
"version": "2.7.1", "version": "2.7.1",
"dev": true, "dev": true,
@ -5567,6 +5773,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/why-is-node-running": { "node_modules/why-is-node-running": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,

5
package.json

@ -20,11 +20,13 @@
"@tailwindcss/forms": "0.5.x", "@tailwindcss/forms": "0.5.x",
"@tailwindcss/typography": "0.5.x", "@tailwindcss/typography": "0.5.x",
"asciidoctor": "3.0.x", "asciidoctor": "3.0.x",
"bech32": "^2.0.0",
"d3": "^7.9.0", "d3": "^7.9.0",
"he": "1.2.x", "he": "1.2.x",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"node-emoji": "^2.2.0", "node-emoji": "^2.2.0",
"nostr-tools": "2.10.x" "nostr-tools": "2.10.x",
"qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.50.1", "@playwright/test": "^1.50.1",
@ -36,6 +38,7 @@
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"@types/he": "1.2.x", "@types/he": "1.2.x",
"@types/node": "22.x", "@types/node": "22.x",
"@types/qrcode": "^1.5.5",
"autoprefixer": "10.x", "autoprefixer": "10.x",
"eslint-plugin-svelte": "2.x", "eslint-plugin-svelte": "2.x",
"flowbite": "2.x", "flowbite": "2.x",

1
src/app.css

@ -2,6 +2,7 @@
@import './styles/scrollbar.css'; @import './styles/scrollbar.css';
@import './styles/publications.css'; @import './styles/publications.css';
@import './styles/visualize.css'; @import './styles/visualize.css';
@import "./styles/events.css";
/* Custom styles */ /* Custom styles */
@layer base { @layer base {

76
src/lib/components/EventDetails.svelte

@ -6,8 +6,8 @@
import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils"; import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils";
import { standardRelays } from "$lib/consts"; import { standardRelays } from "$lib/consts";
import type { NDKEvent } from '$lib/utils/nostrUtils'; import type { NDKEvent } from '$lib/utils/nostrUtils';
import { onMount } from "svelte";
import { getMatchingTags } from '$lib/utils/nostrUtils'; import { getMatchingTags } from '$lib/utils/nostrUtils';
import ProfileHeader from "$components/cards/ProfileHeader.svelte";
const { event, profile = null } = $props<{ const { event, profile = null } = $props<{
event: NDKEvent; event: NDKEvent;
@ -68,8 +68,6 @@
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
{#if event.kind !== 0 && getEventTitle(event)} {#if event.kind !== 0 && getEventTitle(event)}
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{getEventTitle(event)}</h2> <h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{getEventTitle(event)}</h2>
{:else if event.kind === 0 && profile && profile.name}
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{profile.name}</h2>
{/if} {/if}
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
@ -106,71 +104,8 @@
<!-- Content --> <!-- Content -->
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-1">
<span class="text-gray-600 dark:text-gray-400">Content:</span> {#if event.kind !== 0}
{#if event.kind === 0} <span class="text-gray-600 dark:text-gray-400">Content:</span>
{#if profile}
<div class="bg-primary-50 dark:bg-primary-900 rounded-lg p-6 mt-2 shadow flex flex-col gap-4">
<dl class="grid grid-cols-1 gap-y-2">
{#if profile.name}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Name:</dt>
<dd>{profile.name}</dd>
</div>
{/if}
{#if profile.display_name}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Display Name:</dt>
<dd>{profile.display_name}</dd>
</div>
{/if}
{#if profile.about}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">About:</dt>
<dd class="whitespace-pre-line">{profile.about}</dd>
</div>
{/if}
{#if profile.picture}
<div class="flex gap-2 items-center">
<dt class="font-semibold min-w-[120px]">Picture:</dt>
<dd>
<img src={profile.picture} alt="Profile" class="w-16 h-16 rounded-full border" onerror={(e) => { (e.target as HTMLImageElement).src = '/favicon.png'; }} />
</dd>
</div>
{/if}
{#if profile.banner}
<div class="flex gap-2 items-center">
<dt class="font-semibold min-w-[120px]">Banner:</dt>
<dd>
<img src={profile.banner} alt="Banner" class="w-full max-w-xs rounded border" onerror={(e) => { (e.target as HTMLImageElement).src = '/favicon.png'; }} />
</dd>
</div>
{/if}
{#if profile.website}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Website:</dt>
<dd>
<a href={profile.website} target="_blank" class="underline text-primary-700">{profile.website}</a>
</dd>
</div>
{/if}
{#if profile.lud16}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Lightning Address:</dt>
<dd>{profile.lud16}</dd>
</div>
{/if}
{#if profile.nip05}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">NIP-05:</dt>
<dd>{profile.nip05}</dd>
</div>
{/if}
</dl>
</div>
{:else}
<pre class="overflow-x-auto text-xs bg-highlight dark:bg-primary-900 rounded p-2 mt-2">{event.content}</pre>
{/if}
{:else}
<div class="prose dark:prose-invert max-w-none"> <div class="prose dark:prose-invert max-w-none">
{@html showFullContent ? parsedContent : contentPreview} {@html showFullContent ? parsedContent : contentPreview}
{#if !showFullContent && parsedContent.length > 250} {#if !showFullContent && parsedContent.length > 250}
@ -180,6 +115,11 @@
{/if} {/if}
</div> </div>
<!-- If event is profile -->
{#if event.kind === 0}
<ProfileHeader {event} {profile} />
{/if}
<!-- Tags Array --> <!-- Tags Array -->
{#if event.tags && event.tags.length} {#if event.tags && event.tags.length}
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-1">

2
src/lib/components/Preview.svelte

@ -4,7 +4,7 @@
import { CaretDownSolid, CaretUpSolid, EditOutline } from 'flowbite-svelte-icons'; import { CaretDownSolid, CaretUpSolid, EditOutline } from 'flowbite-svelte-icons';
import Self from './Preview.svelte'; import Self from './Preview.svelte';
import { contentParagraph, sectionHeading } from '$lib/snippets/PublicationSnippets.svelte'; import { contentParagraph, sectionHeading } from '$lib/snippets/PublicationSnippets.svelte';
import BlogHeader from "./blog/BlogHeader.svelte"; import BlogHeader from "$components/cards/BlogHeader.svelte";
// TODO: Fix move between parents. // TODO: Fix move between parents.

2
src/lib/components/Publication.svelte

@ -18,7 +18,7 @@
import type { PublicationTree } from "$lib/data_structures/publication_tree"; import type { PublicationTree } from "$lib/data_structures/publication_tree";
import Details from "$components/util/Details.svelte"; import Details from "$components/util/Details.svelte";
import { publicationColumnVisibility } from "$lib/stores"; import { publicationColumnVisibility } from "$lib/stores";
import BlogHeader from "$components/blog/BlogHeader.svelte"; import BlogHeader from "$components/cards/BlogHeader.svelte";
import Interactions from "$components/util/Interactions.svelte"; import Interactions from "$components/util/Interactions.svelte";
import TocToggle from "$components/util/TocToggle.svelte"; import TocToggle from "$components/util/TocToggle.svelte";
import { pharosInstance } from '$lib/parser'; import { pharosInstance } from '$lib/parser';

0
src/lib/components/blog/BlogHeader.svelte → src/lib/components/cards/BlogHeader.svelte

113
src/lib/components/cards/ProfileHeader.svelte

@ -0,0 +1,113 @@
<script lang="ts">
import { Card, Img, Modal, Button, P } from "flowbite-svelte";
import { onMount } from "svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { type NostrProfile, toNpub } from "$lib/utils/nostrUtils.ts";
import QrCode from "$components/util/QrCode.svelte";
import CopyToClipboard from "$components/util/CopyToClipboard.svelte";
import { bech32 } from 'bech32';
import type { NDKEvent } from "@nostr-dev-kit/ndk";
const { event, profile } = $props<{ event: NDKEvent, profile: NostrProfile }>();
let lnModalOpen = $state(false);
let lnurl = $state<string | null>(null);
onMount(async () => {
if (profile?.lud16) {
try {
// Convert LN address to LNURL
const [name, domain] = profile?.lud16.split('@');
const url = `https://${domain}/.well-known/lnurlp/${name}`;
const words = bech32.toWords(new TextEncoder().encode(url));
lnurl = bech32.encode('lnurl', words);
} catch {
console.log('Error converting LN address to LNURL');
}
}
});
</script>
{#if profile}
<Card class="ArticleBox card-leather w-full max-w-2xl">
<div class='space-y-4'>
{#if profile.banner}
<div class="ArticleBoxImage flex col justify-center">
<Img src={profile.banner} class="rounded w-full max-h-72 object-cover" alt="Profile banner" onerror={(e) => { (e.target as HTMLImageElement).style.display = 'none';}} />
</div>
{/if}
<div class='flex flex-row space-x-4 items-center'>
{#if profile.picture}
<img src={profile.picture} alt="Profile avatar" class="w-16 h-16 rounded-full border" onerror={(e) => { (e.target as HTMLImageElement).src = '/favicon.png'; }} />
{/if}
{@render userBadge(toNpub(event.pubkey) as string, profile.displayName || profile.name || event.pubkey)}
</div>
<div>
<div class="mt-2 flex flex-col gap-4">
<dl class="grid grid-cols-1 gap-y-2">
{#if profile.name}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Name:</dt>
<dd>{profile.name}</dd>
</div>
{/if}
{#if profile.displayName}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Display Name:</dt>
<dd>{profile.displayName}</dd>
</div>
{/if}
{#if profile.about}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">About:</dt>
<dd class="whitespace-pre-line">{profile.about}</dd>
</div>
{/if}
{#if profile.website}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">Website:</dt>
<dd>
<a href={profile.website} target="_blank" class="underline text-primary-700 dark:text-primary-200">{profile.website}</a>
</dd>
</div>
{/if}
{#if profile.nip05}
<div class="flex gap-2">
<dt class="font-semibold min-w-[120px]">NIP-05:</dt>
<dd>{profile.nip05}</dd>
</div>
{/if}
{#if profile.lud16}
<div class="flex items-center gap-2 mt-4">
<dt class="font-semibold min-w-[120px]">Lightning Address:</dt>
<dd><Button class="btn-leather" color="primary" outline onclick={() => lnModalOpen = true}>{profile.lud16}</Button> </dd>
</div>
{/if}
</dl>
</div>
</div>
</div>
</Card>
<Modal class='modal-leather' title='Lightning Address' bind:open={lnModalOpen} outsideclose size='sm'>
{#if profile.lud16}
<div>
<div class='flex flex-col items-center'>
{@render userBadge(toNpub(event.pubkey) as string, profile?.displayName || profile.name || event.pubkey)}
<P>{profile.lud16}</P>
</div>
<div class="flex flex-col items-center mt-3 space-y-4">
<P>Scan the QR code or copy the address</P>
{#if lnurl}
<P style="overflow-wrap: anywhere">
<CopyToClipboard icon={false} displayText={lnurl}></CopyToClipboard>
</P>
<QrCode value={lnurl} />
{:else}
<P>Couldn't generate address.</P>
{/if}
</div>
</div>
{/if}
</Modal>
{/if}

5
src/lib/components/util/CopyToClipboard.svelte

@ -1,11 +1,12 @@
<script lang='ts'> <script lang='ts'>
import { ClipboardCheckOutline, ClipboardCleanOutline } from "flowbite-svelte-icons"; import { ClipboardCheckOutline, ClipboardCleanOutline } from "flowbite-svelte-icons";
import { withTimeout } from "$lib/utils/nostrUtils"; import { withTimeout } from "$lib/utils/nostrUtils";
import type { Component } from "svelte";
let { displayText, copyText = displayText, icon = ClipboardCleanOutline } = $props<{ let { displayText, copyText = displayText, icon = ClipboardCleanOutline } = $props<{
displayText: string; displayText: string;
copyText?: string; copyText?: string;
icon?: typeof ClipboardCleanOutline; icon?: Component | false;
}>(); }>();
let copied: boolean = $state(false); let copied: boolean = $state(false);
@ -34,7 +35,7 @@
<ClipboardCheckOutline class="inline mr-2" /> Copied! <ClipboardCheckOutline class="inline mr-2" /> Copied!
{:else} {:else}
{#if icon} {#if icon}
<icon class="inline mr-2"></icon> <svelte:component this={icon} class="inline mr-2" />
{/if} {/if}
{displayText} {displayText}
{/if} {/if}

17
src/lib/components/util/QrCode.svelte

@ -0,0 +1,17 @@
<script lang="ts">
import { onMount } from 'svelte';
import QRCode from 'qrcode';
export let value: string;
let canvas: HTMLCanvasElement;
async function renderQR() {
if (canvas && value) {
await QRCode.toCanvas(canvas, value, { width: 240 });
}
}
onMount(renderQR);
</script>
<canvas class="qr-code" bind:this={canvas} />

5
src/styles/events.css

@ -0,0 +1,5 @@
@layer components {
canvas.qr-code {
@apply block mx-auto my-4;
}
}
Loading…
Cancel
Save