add basic reactions

This commit is contained in:
@s.roertgen 2025-04-30 09:46:55 +02:00
parent 7a0d33fa9d
commit bc23755758
6 changed files with 185 additions and 62 deletions

81
package-lock.json generated
View file

@ -11,7 +11,9 @@
"@nostr-dev-kit/ndk": "^2.14.5",
"@nostr-dev-kit/ndk-svelte": "^2.4.10",
"daisyui": "^5.0.28",
"qrcode": "^1.5.4"
"emoji-picker-element": "^1.26.3",
"qrcode": "^1.5.4",
"svelte-emoji-selector": "^1.0.1"
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
@ -603,6 +605,39 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
"integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
"integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
"integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.36"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -1814,6 +1849,11 @@
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"node_modules/emoji-picker-element": {
"version": "1.26.3",
"resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.26.3.tgz",
"integrity": "sha512-fOMG44d/3OqTe1pPqlu5H4ZtWg7gK4Le6Bt24JTKtDyce5+EO3Mo8WA95cKHbPSsSsg7ehM12M1x3Y6U6fgvTQ=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -2088,6 +2128,11 @@
"node": ">=0.10.0"
}
},
"node_modules/fa-svelte": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fa-svelte/-/fa-svelte-3.1.0.tgz",
"integrity": "sha512-RqBOWwt7sc+ta9GFjbu5GOwKFRzn3rMPPSqvSGpIwsfVnpMjiI5ttv84lwNsCMEYI6/lu/iH21HUcE3TLz8RGQ=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -2930,6 +2975,16 @@
"node": ">=10.13.0"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@ -3446,6 +3501,25 @@
"typescript": ">=5.0.0"
}
},
"node_modules/svelte-click-outside": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svelte-click-outside/-/svelte-click-outside-1.0.0.tgz",
"integrity": "sha512-TVDn5Vd8L0WI0Y9BFh/2I7judkIqYCbFKkGwGl/f8D0inwBFNyU0weKhrbJY4VQtYnWriq0NPl+mIYGisgALbw=="
},
"node_modules/svelte-emoji-selector": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/svelte-emoji-selector/-/svelte-emoji-selector-1.0.1.tgz",
"integrity": "sha512-gGjDydt+79YQIdUyz/r1sHSkjLko2rb9qHNiBveC5RSl6rJ0mob4T5DrADRArjQ/HA8kNfEJFyqbnLoA+dyLqA==",
"deprecated": "This package is no longer supported",
"dependencies": {
"@fortawesome/free-regular-svg-icons": "^5.10.1",
"@fortawesome/free-solid-svg-icons": "^5.10.1",
"fa-svelte": "^3.0.0",
"popper.js": "^1.15.0",
"svelte-click-outside": "^1.0.0",
"svelte-tabs": "^1.1.0"
}
},
"node_modules/svelte-eslint-parser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.1.3.tgz",
@ -3474,6 +3548,11 @@
}
}
},
"node_modules/svelte-tabs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/svelte-tabs/-/svelte-tabs-1.1.0.tgz",
"integrity": "sha512-bCynxgET2uvqpB6xf/dVyqHjzmumRURQyh2QqXlrki8NxzO7h2WghF8qgpb5qeB5NTX1bMU+9Q5Hf5ey2WLaMg=="
},
"node_modules/tailwindcss": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",

View file

@ -37,6 +37,8 @@
"@nostr-dev-kit/ndk": "^2.14.5",
"@nostr-dev-kit/ndk-svelte": "^2.4.10",
"daisyui": "^5.0.28",
"qrcode": "^1.5.4"
"emoji-picker-element": "^1.26.3",
"qrcode": "^1.5.4",
"svelte-emoji-selector": "^1.0.1"
}
}

View file

@ -1,7 +1,43 @@
<script>
export let event;
let { event } = $props();
import { onMount } from 'svelte';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { ndk, ndkReady, user } from '$lib/stores';
import { writable } from 'svelte/store';
import { login } from '$lib';
let reactions = writable([]);
async function sendReaction() {
const reactionEvent = new NDKEvent($ndk, {
kind: 7,
content: '👍',
tags: [['e', event.id]]
});
await reactionEvent.publish();
}
onMount(() => {
if (!$user) {
console.log('no user, logging in');
login();
}
});
$effect(() => {
if ($ndkReady) {
const sub = $ndk.subscribe({ kinds: [7], '#e': [event.id] });
sub.on('event', (e) => {
$reactions = [...$reactions, e];
console.log(`${e.content}`);
});
}
});
</script>
<div>
<p>{event.content}</p>
<div class="w-full border p-2">
<p>{event.content}</p>
<button onclick={() => sendReaction()}>👍</button>
<p>Reaction Count: {$reactions.length}</p>
</div>

View file

@ -1 +1,35 @@
// place files you want to import through the `$lib` alias in this folder.
import { NDKNip07Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { ndk as ndkStore, user as userStore } from "$lib/stores";
import { get } from "svelte/store";
export async function login() {
let ndk = get(ndkStore)
let user = get(userStore)
if (window.nostr) {
const signer = new NDKNip07Signer();
ndk.signer = signer;
const signedUser = await signer.user();
userStore.set(signedUser)
} else {
const storedPrivateKey = window.localStorage.getItem('nostrPrivateKey');
if (storedPrivateKey) {
const privateKey = JSON.parse(storedPrivateKey);
console.log("stored private key", privateKey)
const signer = new NDKPrivateKeySigner(privateKey);
ndk.signer = signer;
const signedUser = await signer.user();
userStore.set(signedUser)
} else {
console.log('No private key found, generating a new one...');
const privateKey = NDKPrivateKeySigner.generate();
const signer = new NDKPrivateKeySigner(privateKey.privateKey);
console.log('Generated Private Key:', privateKey);
ndk.signer = signer;
const signedUser = await signer.user();
userStore.set(signedUser)
window.localStorage.setItem('nostrPrivateKey', JSON.stringify(privateKey.privateKey));
}
}
}

View file

@ -4,6 +4,7 @@
import { NDKEvent, NDKNip07Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { writable } from 'svelte/store';
import QRCode from 'qrcode';
import { login } from '$lib';
let question = '';
let questionId = writable(''); ;
@ -49,31 +50,7 @@
}, 1000);
}
async function login() {
if (window.nostr) {
const signer = new NDKNip07Signer();
$ndk.signer = signer;
$user = await signer.user();
} else {
const storedPrivateKey = window.localStorage.getItem('nostrPrivateKey');
if (storedPrivateKey) {
const privateKey = JSON.parse(storedPrivateKey);
console.log("stored private key", privateKey)
const signer = new NDKPrivateKeySigner(privateKey);
$ndk.signer = signer;
$user = await signer.user();
} else {
console.log('No private key found, generating a new one...');
const privateKey = NDKPrivateKeySigner.generate();
const signer = new NDKPrivateKeySigner(privateKey.privateKey);
console.log('Generated Private Key:', privateKey);
$ndk.signer = signer;
$user = await signer.user();
window.localStorage.setItem('nostrPrivateKey', JSON.stringify(privateKey.privateKey));
}
}
}
$effect(() => {
if ($ndkReady) {
if ($ndk.activeUser) {

View file

@ -11,18 +11,18 @@
const commentEvent = new NDKEvent($ndk, {
kind: 2222,
content: comment,
tags: [["E", data.id]]
tags: [['E', data.id]]
});
commentEvent.publish();
}
let comment = ""
let comment = '';
let comments = writable([]);
let question = writable("")
let question = writable('');
$effect(() => {
if ($ndkReady) {
const sub = $ndk.subscribe({ kinds: [2222], "#E": [data.id] });
const sub = $ndk.subscribe({ kinds: [2222], '#E': [data.id] });
sub.on('event', (event) => {
$comments = [...$comments, event];
console.log(`${event.content}`);
@ -31,33 +31,28 @@
});
</script>
<div class="w-full flex flex-col justify-center mx-auto items-center">
<h1 class="">Identifier: {data.id}</h1>
<div>{@html data.id}</div>
{#await $ndk.fetchEvent(data.id) then question}
{#if question}
<div class="mb-4 w-full rounded border p-2">
<h2>Question: {question.content}</h2>
<p>Tags: {question.tags}</p>
<p>Created At: {new Date(question.created_at * 1000).toLocaleString()}</p>
</div>
<div>
<h1 class="text-xl">Ideensammlung</h1>
<textarea bind:value={comment} placeholder="Mein Kommentar"></textarea>
<button class="btn btn-error" onclick={() => submitComment()}>Absenden</button>
</div>
{:else}
<p>Loading...</p>
{/if}
{:catch error}
<p>Error fetching question: {error.message}</p>
{/await}
{#each $comments as event}
<Comment {event} />
{/each}
<div class="mx-auto flex flex-col items-center justify-center w-3/4">
{#await $ndk.fetchEvent(data.id) then question}
{#if question}
<div class="mb-4 w-full rounded border p-2">
<h2>Question: {question.content}</h2>
</div>
<div class="w-full flex flex-col items-center justify-center gap-2 mb-2">
<h1 class="text-xl">Ideensammlung</h1>
<textarea class="w-full border" bind:value={comment} placeholder="Mein Kommentar"></textarea>
<button class="btn btn-success" onclick={() => submitComment()}>Absenden</button>
</div>
{:else}
<p>Loading...</p>
{/if}
{:catch error}
<p>Error fetching question: {error.message}</p>
{/await}
<div class="mx-auto flex w-full flex-col items-center justify-center gap-2">
{#each $comments as event}
<Comment {event} />
{/each}
</div>
</div>