forked from coracle/flotilla
Add polls
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import {now, removeUndefined, uniq} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {getTagValue, getTags, getTagValues} from "@welshman/util"
|
||||
|
||||
export type PollType = "singlechoice" | "multiplechoice"
|
||||
|
||||
export type PollOption = {
|
||||
id: string
|
||||
label: string
|
||||
votes: number
|
||||
}
|
||||
|
||||
export const getPollType = (event: TrustedEvent): PollType =>
|
||||
getTagValue("polltype", event.tags) === "multiplechoice" ? "multiplechoice" : "singlechoice"
|
||||
|
||||
export const getPollOptions = (event: TrustedEvent) =>
|
||||
removeUndefined(
|
||||
getTags("option", event.tags).map(tag => {
|
||||
const [, id, label = id] = tag
|
||||
|
||||
if (!id) return undefined
|
||||
|
||||
return {id, label}
|
||||
}),
|
||||
)
|
||||
|
||||
export const getPollEndsAt = (event: TrustedEvent) => {
|
||||
const endsAt = getTagValue("endsAt", event.tags)
|
||||
|
||||
if (!endsAt) return undefined
|
||||
|
||||
const timestamp = parseInt(endsAt)
|
||||
|
||||
return Number.isNaN(timestamp) ? undefined : timestamp
|
||||
}
|
||||
|
||||
export const isPollClosed = (event: TrustedEvent) => {
|
||||
const endsAt = getPollEndsAt(event)
|
||||
|
||||
return typeof endsAt === "number" ? endsAt <= now() : false
|
||||
}
|
||||
|
||||
export const getPollResponseSelections = (event: TrustedEvent, pollType = getPollType(event)) => {
|
||||
const selections = getTagValues("response", event.tags)
|
||||
|
||||
return pollType === "singlechoice" ? selections.slice(0, 1) : uniq(selections)
|
||||
}
|
||||
|
||||
export const getPollResults = (event: TrustedEvent, responses: TrustedEvent[]) => {
|
||||
const options = getPollOptions(event).map(option => ({...option, votes: 0}))
|
||||
const counts = new Map(options.map(option => [option.id, option]))
|
||||
const latestByPubkey = new Map<string, TrustedEvent>()
|
||||
|
||||
for (const response of responses) {
|
||||
const current = latestByPubkey.get(response.pubkey)
|
||||
|
||||
if (!current || response.created_at > current.created_at) {
|
||||
latestByPubkey.set(response.pubkey, response)
|
||||
}
|
||||
}
|
||||
|
||||
for (const response of latestByPubkey.values()) {
|
||||
for (const optionId of getPollResponseSelections(response, getPollType(event))) {
|
||||
const option = counts.get(optionId)
|
||||
|
||||
if (option) {
|
||||
option.votes += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
options,
|
||||
voters: latestByPubkey.size,
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {page} from "$app/stores"
|
||||
import {nthEq} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {getAddress} from "@welshman/util"
|
||||
import {Poll} from "nostr-tools/kinds"
|
||||
import {tracker, userMessagingRelayList} from "@welshman/app"
|
||||
import {identity} from "@welshman/lib"
|
||||
import {
|
||||
@@ -90,6 +91,8 @@ export const makeClassifiedPath = (url: string, address?: string) =>
|
||||
export const makeCalendarPath = (url: string, address?: string) =>
|
||||
makeSpacePath(url, "calendar", address)
|
||||
|
||||
export const makePollPath = (url: string, id?: string) => makeSpacePath(url, "polls", id)
|
||||
|
||||
export const scrollToEvent = (id: string) => {
|
||||
const element = document.querySelector(`[data-event="${id}"]`) as any
|
||||
|
||||
@@ -146,6 +149,10 @@ export const getEventPath = (event: TrustedEvent, urls: string[]) => {
|
||||
return makeCalendarPath(url, getAddress(event))
|
||||
}
|
||||
|
||||
if (event.kind === Poll) {
|
||||
return makePollPath(url, event.id)
|
||||
}
|
||||
|
||||
if (event.kind === MESSAGE) {
|
||||
return makeMessagePath(url, event)
|
||||
}
|
||||
@@ -192,5 +199,7 @@ export const getRoomItemPath = (url: string, event: TrustedEvent) => {
|
||||
return makeGoalPath(url, event.id)
|
||||
case EVENT_TIME:
|
||||
return makeCalendarPath(url, getAddress(event))
|
||||
case Poll:
|
||||
return makePollPath(url, event.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ const staticTitles = new Map<string, string>([
|
||||
["/spaces/[relay]/classifieds", "Classifieds"],
|
||||
["/spaces/[relay]/calendar", "Calendar"],
|
||||
["/spaces/[relay]/goals", "Goals"],
|
||||
["/spaces/[relay]/polls", "Polls"],
|
||||
["/chat", "Messages"],
|
||||
["/join", "Join Space"],
|
||||
["/people", "Find People"],
|
||||
@@ -35,6 +36,7 @@ const eventRoutes = new Set([
|
||||
"/spaces/[relay]/goals/[id]",
|
||||
"/spaces/[relay]/calendar/[address]",
|
||||
"/spaces/[relay]/classifieds/[address]",
|
||||
"/spaces/[relay]/polls/[id]",
|
||||
])
|
||||
|
||||
type RouteParams = Record<string, string | undefined>
|
||||
|
||||
Reference in New Issue
Block a user