mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Add status to alert items
This commit is contained in:
2
.ackrc
2
.ackrc
@@ -3,4 +3,6 @@
|
|||||||
--ignore-dir=build
|
--ignore-dir=build
|
||||||
--ignore-dir=ios/DerivedData
|
--ignore-dir=ios/DerivedData
|
||||||
--ignore-dir=ios/App/App/public
|
--ignore-dir=ios/App/App/public
|
||||||
|
--ignore-file=match:.svg
|
||||||
|
--ignore-file=match:package-lock.json
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {ctx, uniq, equals} from "@welshman/lib"
|
import {ctx, randomId, uniq, equals} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
DELETE,
|
DELETE,
|
||||||
REPORT,
|
REPORT,
|
||||||
@@ -28,8 +28,9 @@ import {
|
|||||||
getRelayTags,
|
getRelayTags,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
toNostrURI,
|
toNostrURI,
|
||||||
|
unionFilters,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, EventContent, EventTemplate} from "@welshman/util"
|
import type {TrustedEvent, Filter, EventContent, EventTemplate} from "@welshman/util"
|
||||||
import {PublishStatus, AuthStatus, SocketStatus} from "@welshman/net"
|
import {PublishStatus, AuthStatus, SocketStatus} from "@welshman/net"
|
||||||
import {Nip59, makeSecret, stamp, Nip46Broker} from "@welshman/signer"
|
import {Nip59, makeSecret, stamp, Nip46Broker} from "@welshman/signer"
|
||||||
import {
|
import {
|
||||||
@@ -61,6 +62,9 @@ import {
|
|||||||
userMembership,
|
userMembership,
|
||||||
INDEXER_RELAYS,
|
INDEXER_RELAYS,
|
||||||
NIP46_PERMS,
|
NIP46_PERMS,
|
||||||
|
ALERT,
|
||||||
|
NOTIFIER_PUBKEY,
|
||||||
|
NOTIFIER_RELAY,
|
||||||
userRoomsByUrl,
|
userRoomsByUrl,
|
||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
import {loadUserData} from "@app/requests"
|
import {loadUserData} from "@app/requests"
|
||||||
@@ -455,3 +459,35 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
|||||||
|
|
||||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||||
publishThunk({event: makeComment(params), relays})
|
publishThunk({event: makeComment(params), relays})
|
||||||
|
|
||||||
|
export type AlertParams = {
|
||||||
|
cron: string
|
||||||
|
email: string
|
||||||
|
relay: string
|
||||||
|
handler: string
|
||||||
|
filters: Filter[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeAlert = async ({cron, email, handler, relay, filters}: AlertParams) =>
|
||||||
|
createEvent(ALERT, {
|
||||||
|
content: await signer
|
||||||
|
.get()
|
||||||
|
.nip44.encrypt(
|
||||||
|
NOTIFIER_PUBKEY,
|
||||||
|
JSON.stringify([
|
||||||
|
["cron", cron],
|
||||||
|
["email", email],
|
||||||
|
["relay", relay],
|
||||||
|
["handler", handler],
|
||||||
|
["channel", "email"],
|
||||||
|
...unionFilters(filters).map(filter => ["filter", JSON.stringify(filter)]),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
tags: [
|
||||||
|
["d", randomId()],
|
||||||
|
["p", NOTIFIER_PUBKEY],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const publishAlert = async (params: AlertParams) =>
|
||||||
|
publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]})
|
||||||
|
|||||||
164
src/app/components/AlertAdd.svelte
Normal file
164
src/app/components/AlertAdd.svelte
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Capacitor} from "@capacitor/core"
|
||||||
|
import {preventDefault} from "@lib/html"
|
||||||
|
import {displayRelayUrl, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
|
||||||
|
import type {Filter} from "@welshman/util"
|
||||||
|
import {pubkey} from "@welshman/app"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import {getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
|
||||||
|
import {loadAlertStatuses} from "@app/requests"
|
||||||
|
import {publishAlert} from "@app/commands"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
|
const handler = Capacitor.isNativePlatform()
|
||||||
|
? "https://app.flotilla.social"
|
||||||
|
: window.location.origin
|
||||||
|
|
||||||
|
const timezone = new Date()
|
||||||
|
.toString()
|
||||||
|
.match(/GMT[^\s]+/)![0]
|
||||||
|
.slice(3)
|
||||||
|
const timezoneOffset = parseInt(timezone) / 100
|
||||||
|
const hour = (17 - timezoneOffset) % 24
|
||||||
|
const WEEKLY = `0 03 ${hour} * * 1`
|
||||||
|
const DAILY = `0 03 ${hour} * * *`
|
||||||
|
|
||||||
|
let loading = false
|
||||||
|
let cron = WEEKLY
|
||||||
|
let email = ""
|
||||||
|
let relay = ""
|
||||||
|
let notifyThreads = true
|
||||||
|
let notifyCalendar = true
|
||||||
|
let notifyChat = false
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (!email.includes("@")) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please provide an email address",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relay) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please select a space",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notifyThreads && !notifyCalendar && !notifyChat) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please select something to be notified about",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters: Filter[] = []
|
||||||
|
|
||||||
|
if (notifyThreads) {
|
||||||
|
filters.push({kinds: [THREAD]})
|
||||||
|
filters.push({kinds: [COMMENT], "#k": [String(THREAD)]})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyCalendar) {
|
||||||
|
filters.push({kinds: [EVENT_TIME]})
|
||||||
|
filters.push({kinds: [COMMENT], "#k": [String(EVENT_TIME)]})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyChat) {
|
||||||
|
filters.push({kinds: [MESSAGE], "#h": getMembershipRoomsByUrl(relay, $userMembership)})
|
||||||
|
}
|
||||||
|
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await publishAlert({cron, email, relay, handler, filters})
|
||||||
|
await loadAlertStatuses($pubkey!)
|
||||||
|
|
||||||
|
pushToast({message: "Your alert has been successfully created!"})
|
||||||
|
back()
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="column gap-4" onsubmit={preventDefault(submit)}>
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
Add an Alert
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Email Address*</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
|
<input placeholder="email@example.com" bind:value={email} />
|
||||||
|
</label>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Frequency*</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<select bind:value={cron} class="select select-bordered">
|
||||||
|
<option value={WEEKLY}>Weekly</option>
|
||||||
|
<option value={DAILY}>Daily</option>
|
||||||
|
</select>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Space*</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<select bind:value={relay} class="select select-bordered">
|
||||||
|
<option value="" disabled selected>Choose a space URL</option>
|
||||||
|
{#each getMembershipUrls($userMembership) as url (url)}
|
||||||
|
<option value={url}>{displayRelayUrl(url)}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Notifications*</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<div class="flex items-center justify-end gap-4">
|
||||||
|
<span class="flex gap-3">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={notifyThreads} />
|
||||||
|
Threads
|
||||||
|
</span>
|
||||||
|
<span class="flex gap-3">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={notifyCalendar} />
|
||||||
|
Calendar
|
||||||
|
</span>
|
||||||
|
<span class="flex gap-3">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={notifyChat} />
|
||||||
|
Chat
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon="alt-arrow-left" />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
|
<Spinner {loading}>Confirm</Spinner>
|
||||||
|
<Icon icon="alt-arrow-right" />
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
21
src/app/components/AlertDelete.svelte
Normal file
21
src/app/components/AlertDelete.svelte
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Confirm from "@lib/components/Confirm.svelte"
|
||||||
|
import type {Alert} from "@app/state"
|
||||||
|
import {NOTIFIER_RELAY} from "@app/state"
|
||||||
|
import {publishDelete} from "@app/commands"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alert: Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
publishDelete({event: alert.event, relays: [NOTIFIER_RELAY]})
|
||||||
|
pushToast({message: "Your alert has been deleted!"})
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Confirm {confirm} message="You'll no longer receive messages for this alert." />
|
||||||
86
src/app/components/AlertItem.svelte
Normal file
86
src/app/components/AlertItem.svelte
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {parseJson, nthEq} from "@welshman/lib"
|
||||||
|
import {
|
||||||
|
getAddress,
|
||||||
|
getTagValue,
|
||||||
|
getTagValues,
|
||||||
|
displayRelayUrl,
|
||||||
|
EVENT_TIME,
|
||||||
|
MESSAGE,
|
||||||
|
THREAD,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import {displayList} from "@lib/util"
|
||||||
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import AlertDelete from "@app/components/AlertDelete.svelte"
|
||||||
|
import type {Alert} from "@app/state"
|
||||||
|
import {alertStatuses} from "@app/state"
|
||||||
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alert: Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
|
const address = $derived(getAddress(alert.event))
|
||||||
|
const status = $derived($alertStatuses.find(s => s.event.tags.some(nthEq(1, address))))
|
||||||
|
const cron = $derived(getTagValue("cron", alert.tags))
|
||||||
|
const channel = $derived(getTagValue("channel", alert.tags))
|
||||||
|
const relay = $derived(getTagValue("relay", alert.tags)!)
|
||||||
|
const filters = $derived(getTagValues("filter", alert.tags).map(parseJson))
|
||||||
|
const types = $derived.by(() => {
|
||||||
|
const t: string[] = []
|
||||||
|
|
||||||
|
if (filters.some(f => f.kinds?.includes(THREAD))) t.push("threads")
|
||||||
|
if (filters.some(f => f.kinds?.includes(EVENT_TIME))) t.push("calendar events")
|
||||||
|
if (filters.some(f => f.kinds?.includes(MESSAGE))) t.push("chat")
|
||||||
|
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
|
||||||
|
const startDelete = () => pushModal(AlertDelete, {alert})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<Button class="py-1" onclick={startDelete}>
|
||||||
|
<Icon icon="trash-bin-2" />
|
||||||
|
</Button>
|
||||||
|
<div class="flex-inline gap-1">
|
||||||
|
{cron?.endsWith("1") ? "Weekly" : "Daily"} alert for
|
||||||
|
{displayList(types)} on
|
||||||
|
<Link class="link" href={makeSpacePath(relay)}>
|
||||||
|
{displayRelayUrl(relay)}
|
||||||
|
</Link>, sent via {channel}.
|
||||||
|
</div>
|
||||||
|
{#if status}
|
||||||
|
{@const statusText = getTagValue("status", status.tags) || "error"}
|
||||||
|
{#if statusText === "ok"}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
|
||||||
|
data-tip={getTagValue("message", status.tags)}>
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
{:else if statusText === "pending"}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
|
||||||
|
data-tip={getTagValue("message", status.tags)}>
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
||||||
|
data-tip={getTagValue("message", status.tags)}>
|
||||||
|
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
||||||
|
data-tip="The notification server did not respond to your request.">
|
||||||
|
Inactive
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
27
src/app/components/Alerts.svelte
Normal file
27
src/app/components/Alerts.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||||
|
import AlertItem from "@app/components/AlertItem.svelte"
|
||||||
|
import {pushModal} from "@app/modal"
|
||||||
|
import {alerts} from "@app/state"
|
||||||
|
|
||||||
|
const startAlert = () => pushModal(AlertAdd)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<strong>Alerts</strong>
|
||||||
|
<Button class="btn btn-primary btn-sm" onclick={startAlert}>
|
||||||
|
<Icon icon="add-circle" />
|
||||||
|
Add Alert
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
{#each $alerts as alert (alert.event.id)}
|
||||||
|
<AlertItem {alert} />
|
||||||
|
{:else}
|
||||||
|
<p class="text-center opacity-75 py-12">No alerts found</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -47,6 +47,9 @@ import {
|
|||||||
import {createScroller} from "@lib/html"
|
import {createScroller} from "@lib/html"
|
||||||
import {daysBetween} from "@lib/util"
|
import {daysBetween} from "@lib/util"
|
||||||
import {
|
import {
|
||||||
|
ALERT,
|
||||||
|
ALERT_STATUS,
|
||||||
|
NOTIFIER_RELAY,
|
||||||
INDEXER_RELAYS,
|
INDEXER_RELAYS,
|
||||||
getDefaultPubkeys,
|
getDefaultPubkeys,
|
||||||
userRoomsByUrl,
|
userRoomsByUrl,
|
||||||
@@ -308,6 +311,20 @@ export const makeCalendarFeed = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Domain specific
|
||||||
|
|
||||||
|
export const loadAlerts = (pubkey: string) =>
|
||||||
|
load({
|
||||||
|
relays: [NOTIFIER_RELAY],
|
||||||
|
filters: [{kinds: [ALERT], authors: [pubkey]}],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const loadAlertStatuses = (pubkey: string) =>
|
||||||
|
load({
|
||||||
|
relays: [NOTIFIER_RELAY],
|
||||||
|
filters: [{kinds: [ALERT_STATUS], "#p": [pubkey]}],
|
||||||
|
})
|
||||||
|
|
||||||
// Application requests
|
// Application requests
|
||||||
|
|
||||||
export const listenForNotifications = () => {
|
export const listenForNotifications = () => {
|
||||||
@@ -361,6 +378,8 @@ export const loadUserData = (
|
|||||||
loadProfile(pubkey, request),
|
loadProfile(pubkey, request),
|
||||||
loadFollows(pubkey, request),
|
loadFollows(pubkey, request),
|
||||||
loadMutes(pubkey, request),
|
loadMutes(pubkey, request),
|
||||||
|
loadAlertStatuses(pubkey),
|
||||||
|
loadAlerts(pubkey),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
normalizeRelayUrl,
|
normalizeRelayUrl,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
||||||
import {Nip59} from "@welshman/signer"
|
import {Nip59, decrypt} from "@welshman/signer"
|
||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
repository,
|
repository,
|
||||||
@@ -62,6 +62,7 @@ import {
|
|||||||
ensurePlaintext,
|
ensurePlaintext,
|
||||||
thunks,
|
thunks,
|
||||||
walkThunks,
|
walkThunks,
|
||||||
|
signer,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import type {Thunk, Relay} from "@welshman/app"
|
import type {Thunk, Relay} from "@welshman/app"
|
||||||
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
||||||
@@ -73,6 +74,15 @@ export const GENERAL = "_"
|
|||||||
|
|
||||||
export const PROTECTED = ["-"]
|
export const PROTECTED = ["-"]
|
||||||
|
|
||||||
|
export const ALERT = 32830
|
||||||
|
|
||||||
|
export const ALERT_STATUS = 32831
|
||||||
|
|
||||||
|
export const NOTIFIER_PUBKEY = "27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df"
|
||||||
|
|
||||||
|
// export const NOTIFIER_RELAY = 'wss://notifier.flotilla.social/'
|
||||||
|
export const NOTIFIER_RELAY = "ws://localhost:4738/"
|
||||||
|
|
||||||
export const INDEXER_RELAYS = [
|
export const INDEXER_RELAYS = [
|
||||||
"wss://purplepag.es/",
|
"wss://purplepag.es/",
|
||||||
"wss://relay.damus.io/",
|
"wss://relay.damus.io/",
|
||||||
@@ -332,6 +342,40 @@ export const {
|
|||||||
load({...request, filters: [{kinds: [SETTINGS], authors: [pubkey]}]}),
|
load({...request, filters: [{kinds: [SETTINGS], authors: [pubkey]}]}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Alerts
|
||||||
|
|
||||||
|
export type Alert = {
|
||||||
|
event: TrustedEvent
|
||||||
|
tags: string[][]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||||
|
filters: [{kinds: [ALERT]}],
|
||||||
|
itemToEvent: item => item.event,
|
||||||
|
eventToItem: async event => {
|
||||||
|
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||||
|
|
||||||
|
return {event, tags}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Alert Statuses
|
||||||
|
|
||||||
|
export type AlertStatus = {
|
||||||
|
event: TrustedEvent
|
||||||
|
tags: string[][]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alertStatuses = deriveEventsMapped<AlertStatus>(repository, {
|
||||||
|
filters: [{kinds: [ALERT_STATUS]}],
|
||||||
|
itemToEvent: item => item.event,
|
||||||
|
eventToItem: async event => {
|
||||||
|
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||||
|
|
||||||
|
return {event, tags}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// Membership
|
// Membership
|
||||||
|
|
||||||
export const hasMembershipUrl = (list: List | undefined, url: string) =>
|
export const hasMembershipUrl = (list: List | undefined, url: string) =>
|
||||||
@@ -356,11 +400,7 @@ export const getMembershipRooms = (list?: List) =>
|
|||||||
getGroupTags(getListTags(list)).map(([_, room, url, name = ""]) => ({url, room, name}))
|
getGroupTags(getListTags(list)).map(([_, room, url, name = ""]) => ({url, room, name}))
|
||||||
|
|
||||||
export const getMembershipRoomsByUrl = (url: string, list?: List) =>
|
export const getMembershipRoomsByUrl = (url: string, list?: List) =>
|
||||||
sort(
|
sort(getGroupTags(getListTags(list)).filter(nthEq(2, url)).map(nth(1)))
|
||||||
getGroupTags(getListTags(list))
|
|
||||||
.filter(t => t[2] === url)
|
|
||||||
.map(nth(1)),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const memberships = deriveEventsMapped<PublishedList>(repository, {
|
export const memberships = deriveEventsMapped<PublishedList>(repository, {
|
||||||
filters: [{kinds: [GROUPS]}],
|
filters: [{kinds: [GROUPS]}],
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<div>{subtitle}</div>
|
<div>{subtitle}</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>{message}</p>
|
<p class="text-center">{message}</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon="alt-arrow-left" />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||||
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
||||||
import InfoKeys from "@app/components/InfoKeys.svelte"
|
import InfoKeys from "@app/components/InfoKeys.svelte"
|
||||||
|
import Alerts from "@app/components/Alerts.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {clip} from "@app/toast"
|
import {clip} from "@app/toast"
|
||||||
@@ -120,6 +121,7 @@
|
|||||||
</FieldInline>
|
</FieldInline>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<Alerts />
|
||||||
<div class="card2 bg-alt col-4 shadow-xl">
|
<div class="card2 bg-alt col-4 shadow-xl">
|
||||||
<Button class="btn btn-outline btn-error" onclick={startDelete}>
|
<Button class="btn btn-outline btn-error" onclick={startDelete}>
|
||||||
<Icon icon="trash-bin-2" />
|
<Icon icon="trash-bin-2" />
|
||||||
|
|||||||
Reference in New Issue
Block a user