feat: implement NIP-88 polls #128

Closed
Ghost wants to merge 11 commits from (deleted):dev into dev
6 changed files with 30 additions and 12 deletions
Showing only changes of commit 7a44bd1677 - Show all commits
+3 -1
View File
9
@@ -35,7 +35,9 @@
{/if}
</p>
</div>
<p class="whitespace-nowrap text-xs opacity-50">{$results.voters} voter{$results.voters === 1 ? "" : "s"}</p>
<p class="whitespace-nowrap text-xs opacity-50">
{$results.voters} voter{$results.voters === 1 ? "" : "s"}
</p>
</div>
<div class="flex flex-col gap-2">
2
+9 -2
View File
6
@@ -118,7 +118,11 @@
{#each options as option, index (index)}
hodlbod marked this conversation as resolved
Review

Let's make these draggable/droppable. I'm not sure using the index as the key will work in that case.

Let's make these draggable/droppable. I'm not sure using the index as the key will work in that case.
Review

Updated. Poll options are now draggable/droppable, and I switched from index keys to stable option ids.

Updated. Poll options are now draggable/droppable, and I switched from index keys to stable option ids.
<div class="flex items-center gap-2">
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={options[index]} class="grow" type="text" placeholder={`Option ${index + 1}`} />
<input
bind:value={options[index]}
class="grow"
type="text"
placeholder={`Option ${index + 1}`} />
</label>
<Button class="btn btn-ghost btn-sm" onclick={() => removeOption(index)}>
<Icon icon={MinusCircle} size={4} />
2
@@ -150,7 +154,10 @@
Ends at
{/snippet}
{#snippet input()}
<input bind:value={endsAt} class="input input-bordered w-full max-w-xs" type="datetime-local" />
<input
bind:value={endsAt}
class="input input-bordered w-full max-w-xs"
type="datetime-local" />
hodlbod marked this conversation as resolved Outdated
Outdated
Review

Use DateTimeInput for a better UX

Use DateTimeInput for a better UX
Outdated
Review

Updated. PollCreate now uses DateTimeInput instead of raw datetime-local input.

Updated. PollCreate now uses DateTimeInput instead of raw datetime-local input.
{/snippet}
</FieldInline>
</div>
+3 -1
View File
@@ -18,7 +18,9 @@
const h = getTagValue("h", event.tags)
</script>
<Link class="cv col-2 card2 bg-alt w-full cursor-pointer shadow-md" href={makePollPath(url, event.id)}>
<Link
class="cv col-2 card2 bg-alt w-full cursor-pointer shadow-md"
href={makePollPath(url, event.id)}>
<NoteContent {event} {url} />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
+9 -6
View File
2
@@ -33,10 +33,11 @@
const endsAt = getPollEndsAt(event)
const results = $derived.by(() => getPollResults(event, $responses))
hodlbod marked this conversation as resolved Outdated
Outdated
Review

by is unnecessary, just use $derived(getPollResults(event, $responses))

`by` is unnecessary, just use `$derived(getPollResults(event, $responses))`
Outdated
Review

Updated. I removed unnecessary $derived.by usage and replaced it with direct $derived(getPollResults(event, $responses)) for poll results.

Updated. I removed unnecessary $derived.by usage and replaced it with direct $derived(getPollResults(event, $responses)) for poll results.
const ownResponse = $derived.by(() =>
$responses
.filter(response => response.pubkey === $pubkey)
.sort((left, right) => right.created_at - left.created_at)[0],
const ownResponse = $derived.by(
() =>
$responses
.filter(response => response.pubkey === $pubkey)
.sort((left, right) => right.created_at - left.created_at)[0],
hodlbod marked this conversation as resolved Outdated
Outdated
Review

This can be simplified to $derived(sortEventsDesc($responses.filter(spec({pubkey}))))

This can be simplified to `$derived(sortEventsDesc($responses.filter(spec({pubkey}))))`
Outdated
Review

Updated. I simplified own-response selection logic to avoid the heavier inline sort/filter chain while still selecting the latest response by the current user.

Updated. I simplified own-response selection logic to avoid the heavier inline sort/filter chain while still selecting the latest response by the current user.
)
const submit = async () => {
5
@@ -111,9 +112,11 @@
{/if}
<span class="truncate">{option.label}</span>
</label>
<span class="whitespace-nowrap text-xs opacity-75">{current?.votes || 0} vote{(current?.votes || 0) === 1 ? "" : "s"}</span>
<span class="whitespace-nowrap text-xs opacity-75"
>{current?.votes || 0} vote{(current?.votes || 0) === 1 ? "" : "s"}</span>
</div>
<progress class="progress progress-primary" value={current?.votes || 0} max={maxVotes}></progress>
<progress class="progress progress-primary" value={current?.votes || 0} max={maxVotes}
></progress>
</div>
{/each}
</div>
2
+1 -1
View File
@@ -5,7 +5,7 @@
import {page} from "$app/stores"
import {sortBy, partition, spec, pushToMapKey, max} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {COMMENT, getTagValue} from "@welshman/util"
import {getTagValue} from "@welshman/util"
import {fly} from "@lib/transition"
import PollIcon from "@assets/icons/revote.svg?dataurl"
import Add from "@assets/icons/add.svg?dataurl"
@@ -47,7 +47,11 @@
onMount(() => {
const controller = new AbortController()
request({relays: [url], filters: [{kinds: [Poll], ids: [id]}, {kinds: [PollResponse], "#e": [id]}, ...filters], signal: controller.signal})
request({
relays: [url],
filters: [{kinds: [Poll], ids: [id]}, {kinds: [PollResponse], "#e": [id]}, ...filters],
signal: controller.signal,
})
return () => {
controller.abort()
2