forked from coracle/flotilla
Avoid reflow by showing chat thunk status in a toast
This commit is contained in:
@@ -22,15 +22,17 @@
|
|||||||
showActivity?: boolean
|
showActivity?: boolean
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeCalendarPath(url, event.id)
|
const path = makeCalendarPath(url, event.id)
|
||||||
|
|
||||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
const {url, header, initialValues}: Props = $props()
|
const {url, header, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
...ed.storage.nostr.getEditorTags(),
|
...ed.storage.nostr.getEditorTags(),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
|
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
||||||
|
|
||||||
const thunk = $thunks[event.id]
|
const thunk = $thunks[event.id]
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const today = formatTimestampAsDate(now())
|
const today = formatTimestampAsDate(now())
|
||||||
const profile = deriveProfile(event.pubkey, [url])
|
const profile = deriveProfile(event.pubkey, [url])
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||||
@@ -42,10 +43,10 @@
|
|||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TapTarget
|
<TapTarget
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<Content minimalQuote {event} {url} />
|
<Content minimalQuote {event} {url} />
|
||||||
{#if thunk}
|
{#if thunk}
|
||||||
<ThunkStatus {thunk} class="mt-2" />
|
<ThunkFailure showToastOnRetry {thunk} class="mt-2" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const onEmoji = async (emoji: NativeEmoji) =>
|
const onEmoji = async (emoji: NativeEmoji) =>
|
||||||
publishReaction({
|
publishReaction({
|
||||||
event,
|
event,
|
||||||
relays: [url],
|
relays: [url],
|
||||||
content: emoji.unicode,
|
content: emoji.unicode,
|
||||||
protect: await canEnforceNip70(url),
|
protect: await shouldProtect,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,15 @@
|
|||||||
|
|
||||||
const {url, event, reply}: Props = $props()
|
const {url, event, reply}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const onEmoji = (async (event: TrustedEvent, url: string, emoji: NativeEmoji) => {
|
const onEmoji = (async (event: TrustedEvent, url: string, emoji: NativeEmoji) => {
|
||||||
history.back()
|
history.back()
|
||||||
publishReaction({
|
publishReaction({
|
||||||
event,
|
event,
|
||||||
relays: [url],
|
relays: [url],
|
||||||
content: emoji.unicode,
|
content: emoji.unicode,
|
||||||
protect: await canEnforceNip70(url),
|
protect: await shouldProtect,
|
||||||
})
|
})
|
||||||
}).bind(undefined, event, url)
|
}).bind(undefined, event, url)
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,11 @@
|
|||||||
pubkey,
|
pubkey,
|
||||||
tagPubkey,
|
tagPubkey,
|
||||||
sendWrapped,
|
sendWrapped,
|
||||||
|
mergeThunks,
|
||||||
loadInboxRelaySelections,
|
loadInboxRelaySelections,
|
||||||
inboxRelaySelectionsByPubkey,
|
inboxRelaySelectionsByPubkey,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
|
import type {AbstractThunk} from "@welshman/app"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
import ChatMessage from "@app/components/ChatMessage.svelte"
|
import ChatMessage from "@app/components/ChatMessage.svelte"
|
||||||
import ChatCompose from "@app/components/ChatCompose.svelte"
|
import ChatCompose from "@app/components/ChatCompose.svelte"
|
||||||
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
||||||
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import {
|
import {
|
||||||
INDEXER_RELAYS,
|
INDEXER_RELAYS,
|
||||||
userSettingValues,
|
userSettingValues,
|
||||||
@@ -53,6 +56,7 @@
|
|||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {prependParent} from "@app/commands"
|
import {prependParent} from "@app/commands"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string
|
id: string
|
||||||
@@ -121,12 +125,23 @@
|
|||||||
|
|
||||||
// Split the message into multiple pieces so that we can use kind 15 to send images per nip 17
|
// Split the message into multiple pieces so that we can use kind 15 to send images per nip 17
|
||||||
// Sleep 1 second between each one to make sure timestamps are distinct
|
// Sleep 1 second between each one to make sure timestamps are distinct
|
||||||
|
const thunks: AbstractThunk[] = []
|
||||||
for (let i = 0; i < templates.length; i++) {
|
for (let i = 0; i < templates.length; i++) {
|
||||||
const template = templates[i]
|
const template = templates[i]
|
||||||
|
|
||||||
await sendWrapped({pubkeys, template, delay: $userSettingValues.send_delay + ms(i)})
|
thunks.push(
|
||||||
|
await sendWrapped({pubkeys, template, delay: $userSettingValues.send_delay + ms(i)}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk: mergeThunks(thunks)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
clearParent()
|
clearParent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
|
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
|
||||||
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
|
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if thunk}
|
{#if thunk}
|
||||||
<ThunkStatus {thunk} class="mt-1" />
|
<ThunkFailure showToastOnRetry {thunk} class="mt-1" />
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
data-event={event.id}
|
data-event={event.id}
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
|
|
||||||
const {url, event, showActivity = false}: Props = $props()
|
const {url, event, showActivity = false}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeThreadPath(url, event.id)
|
const path = makeThreadPath(url, event.id)
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
const {url, noun, event, hideZap, customActions}: Props = $props()
|
const {url, noun, event, hideZap, customActions}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
const showPopover = () => popover?.show()
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
event,
|
event,
|
||||||
content: emoji.unicode,
|
content: emoji.unicode,
|
||||||
relays: [url],
|
relays: [url],
|
||||||
protect: await canEnforceNip70(url),
|
protect: await shouldProtect,
|
||||||
})
|
})
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
|||||||
@@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
const {url, event}: Props = $props()
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const confirm = async () => {
|
const confirm = async () => {
|
||||||
await publishDelete({event, relays: [url], protect: await canEnforceNip70(url)})
|
await publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
clearModals()
|
clearModals()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
const {url, event, onClose, onSubmit} = $props()
|
const {url, event, onClose, onSubmit} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||||
const tags = ed.storage.nostr.getEditorTags()
|
const tags = ed.storage.nostr.getEditorTags()
|
||||||
|
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const reports = deriveEvents(repository, {
|
const reports = deriveEvents(repository, {
|
||||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||||
})
|
})
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const deleteReport = async (report: TrustedEvent) => {
|
const deleteReport = async (report: TrustedEvent) => {
|
||||||
publishDelete({event: report, relays: [url], protect: await canEnforceNip70(url)})
|
publishDelete({event: report, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
if ($reports.length === 0) {
|
if ($reports.length === 0) {
|
||||||
history.back()
|
history.back()
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
|
|
||||||
const {url, event, showActivity = false}: Props = $props()
|
const {url, event, showActivity = false}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeGoalPath(url, event.id)
|
const path = makeGoalPath(url, event.id)
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -50,7 +52,7 @@
|
|||||||
["relays", url],
|
["relays", url],
|
||||||
]
|
]
|
||||||
|
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,20 @@
|
|||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
const onEmoji = async (emoji: NativeEmoji) =>
|
const onEmoji = async (emoji: NativeEmoji) =>
|
||||||
publishReaction({
|
publishReaction({
|
||||||
event,
|
event,
|
||||||
content: emoji.unicode,
|
content: emoji.unicode,
|
||||||
relays: [url],
|
relays: [url],
|
||||||
protect: await canEnforceNip70(url),
|
protect: await shouldProtect,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
|
|
||||||
const {url, event, showActivity = false}: Props = $props()
|
const {url, event, showActivity = false}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeThreadPath(url, event.id)
|
const path = makeThreadPath(url, event.id)
|
||||||
|
|
||||||
const deleteReaction = async (event: TrustedEvent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishDelete({relays: [url], event, protect: await canEnforceNip70(url)})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = async (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
|
|
||||||
const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]]
|
const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]]
|
||||||
|
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {stopPropagation} from "svelte/legacy"
|
||||||
|
import {noop} from "@welshman/lib"
|
||||||
|
import {
|
||||||
|
MergedThunk,
|
||||||
|
publishThunk,
|
||||||
|
isMergedThunk,
|
||||||
|
thunkIsComplete,
|
||||||
|
getFailedThunkUrls,
|
||||||
|
} from "@welshman/app"
|
||||||
|
import type {Thunk} from "@welshman/app"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
|
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
thunk: Thunk | MergedThunk
|
||||||
|
showToastOnRetry?: boolean
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||||
|
|
||||||
|
const retry = () => {
|
||||||
|
thunk = isMergedThunk(thunk)
|
||||||
|
? new MergedThunk(thunk.thunks.map(t => publishThunk(t.options)))
|
||||||
|
: publishThunk(thunk.options)
|
||||||
|
|
||||||
|
if (showToastOnRetry) {
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const failedUrls = $derived(getFailedThunkUrls($thunk))
|
||||||
|
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if showFailure}
|
||||||
|
{@const url = failedUrls[0]}
|
||||||
|
{@const status = $thunk.status[url]}
|
||||||
|
{@const message = $thunk.details[url]}
|
||||||
|
<button
|
||||||
|
class="flex w-full justify-end px-1 text-xs {restProps.class}"
|
||||||
|
onclick={stopPropagation(noop)}>
|
||||||
|
<Tippy
|
||||||
|
class="flex items-center"
|
||||||
|
component={ThunkStatusDetail}
|
||||||
|
props={{url, message, status, retry}}
|
||||||
|
params={{interactive: true}}>
|
||||||
|
{#snippet children()}
|
||||||
|
<span class="flex cursor-pointer items-center gap-1 text-error">
|
||||||
|
<Icon icon="danger" size={3} />
|
||||||
|
<span>Failed to send!</span>
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</Tippy>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {stopPropagation} from "svelte/legacy"
|
||||||
|
import {PublishStatus} from "@welshman/net"
|
||||||
|
import type {AbstractThunk} from "@welshman/app"
|
||||||
|
import {abortThunk, thunkHasStatus} from "@welshman/app"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
thunk: AbstractThunk
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {thunk, ...restProps}: Props = $props()
|
||||||
|
|
||||||
|
const abort = () => abortThunk(thunk)
|
||||||
|
|
||||||
|
const isSending = $derived(thunkHasStatus(PublishStatus.Sending, $thunk))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-end px-1 text-xs {restProps.class}">
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
||||||
|
<span class="opacity-50">Sending...</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="underline transition-all"
|
||||||
|
class:link={isSending}
|
||||||
|
class:opacity-25={!isSending}
|
||||||
|
onclick={stopPropagation(abort)}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
@@ -1,95 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {stopPropagation} from "svelte/legacy"
|
import {MergedThunk, thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
|
||||||
import {nth, noop} from "@welshman/lib"
|
|
||||||
import {PublishStatus} from "@welshman/net"
|
|
||||||
import {
|
|
||||||
MergedThunk,
|
|
||||||
publishThunk,
|
|
||||||
isMergedThunk,
|
|
||||||
thunkIsComplete,
|
|
||||||
thunkHasStatus,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import type {Thunk} from "@welshman/app"
|
import type {Thunk} from "@welshman/app"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import ThunkPending from "@app/components/ThunkPending.svelte"
|
||||||
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
|
|
||||||
import {userSettingValues} from "@app/state"
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
thunk: Thunk | MergedThunk
|
thunk: Thunk | MergedThunk
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
let {thunk, ...restProps}: Props = $props()
|
const {thunk, ...restProps}: Props = $props()
|
||||||
|
|
||||||
const abort = () => thunk.controller.abort()
|
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
|
||||||
|
const showPending = $derived(!thunkIsComplete($thunk))
|
||||||
const retry = () => {
|
|
||||||
thunk = isMergedThunk(thunk)
|
|
||||||
? new MergedThunk(thunk.thunks.map(t => publishThunk(t.options)))
|
|
||||||
: publishThunk(thunk.options)
|
|
||||||
}
|
|
||||||
|
|
||||||
const statuses = $derived(Object.entries($thunk.status))
|
|
||||||
const isSending = $derived(thunkHasStatus($thunk, PublishStatus.Sending))
|
|
||||||
const canCancel = $derived(isSending && $userSettingValues.send_delay > 0)
|
|
||||||
const failedUrls = $derived(
|
|
||||||
statuses
|
|
||||||
.filter(([_, status]) => [PublishStatus.Failure, PublishStatus.Timeout].includes(status))
|
|
||||||
.map(nth(0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
|
|
||||||
|
|
||||||
let isPending = $state(thunkHasStatus($thunk, PublishStatus.Pending))
|
|
||||||
|
|
||||||
const showPending = $derived(canCancel || isPending)
|
|
||||||
|
|
||||||
// Delay updating isPending so users can see that the message is sent
|
|
||||||
$effect(() => {
|
|
||||||
isPending = isPending || thunkHasStatus($thunk, PublishStatus.Pending)
|
|
||||||
|
|
||||||
if (!thunkHasStatus($thunk, PublishStatus.Pending)) {
|
|
||||||
setTimeout(() => {
|
|
||||||
isPending = false
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showFailure}
|
{#if showFailure}
|
||||||
{@const url = failedUrls[0]}
|
<ThunkFailure class={restProps.class} {thunk} />
|
||||||
{@const status = $thunk.status[url]}
|
|
||||||
{@const message = $thunk.details[url]}
|
|
||||||
<button
|
|
||||||
class="flex w-full justify-end px-1 text-xs {restProps.class}"
|
|
||||||
onclick={stopPropagation(noop)}>
|
|
||||||
<Tippy
|
|
||||||
class="flex items-center"
|
|
||||||
component={ThunkStatusDetail}
|
|
||||||
props={{url, message, status, retry}}
|
|
||||||
params={{interactive: true}}>
|
|
||||||
{#snippet children()}
|
|
||||||
<span class="flex cursor-pointer items-center gap-1 text-error">
|
|
||||||
<Icon icon="danger" size={3} />
|
|
||||||
<span>Failed to send!</span>
|
|
||||||
</span>
|
|
||||||
{/snippet}
|
|
||||||
</Tippy>
|
|
||||||
</button>
|
|
||||||
{:else if showPending}
|
{:else if showPending}
|
||||||
<div class="flex w-full justify-end px-1 text-xs {restProps.class}">
|
<ThunkPending class={restProps.class} {thunk} />
|
||||||
<span class="flex items-center gap-1">
|
|
||||||
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
|
||||||
<span class="opacity-50">Sending...</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="underline transition-all"
|
|
||||||
class:link={canCancel}
|
|
||||||
class:opacity-25={!canCancel}
|
|
||||||
onclick={stopPropagation(abort)}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {AbstractThunk} from "@welshman/app"
|
||||||
|
import {thunkHasStatus, thunkIsComplete} from "@welshman/app"
|
||||||
|
import {PublishStatus} from "@welshman/net"
|
||||||
|
import ThunkPending from "@app/components/ThunkPending.svelte"
|
||||||
|
import type {Toast} from "@app/toast"
|
||||||
|
import {popToast} from "@app/toast"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
toast: Toast
|
||||||
|
thunk: AbstractThunk
|
||||||
|
}
|
||||||
|
|
||||||
|
const {toast, ...props}: Props = $props()
|
||||||
|
|
||||||
|
const id = toast.id
|
||||||
|
const thunk = props.thunk
|
||||||
|
const {Aborted, Timeout, Failure} = PublishStatus
|
||||||
|
const isFailure = $derived(thunkHasStatus([Aborted, Timeout, Failure], $thunk))
|
||||||
|
const isComplete = $derived(thunkIsComplete($thunk))
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isFailure) {
|
||||||
|
popToast(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isComplete) {
|
||||||
|
setTimeout(() => popToast(id), 2000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !isComplete}
|
||||||
|
<ThunkPending {thunk} />
|
||||||
|
{:else if !isFailure}
|
||||||
|
<p class="text-xs opacity-75">Message sent!</p>
|
||||||
|
{/if}
|
||||||
@@ -21,12 +21,17 @@
|
|||||||
class:bg-base-100={theme === "info"}
|
class:bg-base-100={theme === "info"}
|
||||||
class:text-base-content={theme === "info"}
|
class:text-base-content={theme === "info"}
|
||||||
class:alert-error={theme === "error"}>
|
class:alert-error={theme === "error"}>
|
||||||
<p class="welshman-content-error">
|
<p class:welshman-content-error={theme === "error"}>
|
||||||
{@html renderAsHtml(parse({content: $toast.message}))}
|
{#if $toast.message}
|
||||||
{#if $toast.action}
|
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||||
<Button class="cursor-pointer underline" onclick={onActionClick}>
|
{#if $toast.action}
|
||||||
{$toast.action.message}
|
<Button class="cursor-pointer underline" onclick={onActionClick}>
|
||||||
</Button>
|
{$toast.action.message}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{:else if $toast.children}
|
||||||
|
{@const {component: Component, props} = $toast?.children || {}}
|
||||||
|
<Component toast={$toast} {...props} />
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
||||||
|
|||||||
+2
-2
@@ -86,7 +86,7 @@ import {
|
|||||||
userFollows,
|
userFollows,
|
||||||
ensurePlaintext,
|
ensurePlaintext,
|
||||||
thunks,
|
thunks,
|
||||||
walkThunks,
|
flattenThunks,
|
||||||
signer,
|
signer,
|
||||||
makeOutboxLoader,
|
makeOutboxLoader,
|
||||||
appContext,
|
appContext,
|
||||||
@@ -260,7 +260,7 @@ export const getUrlsForEvent = derived([trackerStore, thunks], ([$tracker, $thun
|
|||||||
const getThunksByEventId = memoize(() => {
|
const getThunksByEventId = memoize(() => {
|
||||||
const thunksByEventId = new Map<string, Thunk[]>()
|
const thunksByEventId = new Map<string, Thunk[]>()
|
||||||
|
|
||||||
for (const thunk of walkThunks(Object.values($thunks))) {
|
for (const thunk of flattenThunks(Object.values($thunks))) {
|
||||||
pushToMapKey(thunksByEventId, thunk.event.id, thunk)
|
pushToMapKey(thunksByEventId, thunk.event.id, thunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -1,11 +1,16 @@
|
|||||||
|
import type {Component} from "svelte"
|
||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {randomId} from "@welshman/lib"
|
import {randomId} from "@welshman/lib"
|
||||||
import {copyToClipboard} from "@lib/html"
|
import {copyToClipboard} from "@lib/html"
|
||||||
|
|
||||||
export type ToastParams = {
|
export type ToastParams = {
|
||||||
message: string
|
message?: string
|
||||||
timeout?: number
|
timeout?: number
|
||||||
theme?: "error"
|
theme?: "error"
|
||||||
|
children?: {
|
||||||
|
component: Component<any>
|
||||||
|
props: Record<string, any>
|
||||||
|
}
|
||||||
action?: {
|
action?: {
|
||||||
message: string
|
message: string
|
||||||
onclick: () => void
|
onclick: () => void
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
import PageBar from "@lib/components/PageBar.svelte"
|
import PageBar from "@lib/components/PageBar.svelte"
|
||||||
import PageContent from "@lib/components/PageContent.svelte"
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||||
import ChannelName from "@app/components/ChannelName.svelte"
|
import ChannelName from "@app/components/ChannelName.svelte"
|
||||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
const channel = deriveChannel(url, room)
|
const channel = deriveChannel(url, room)
|
||||||
const filter = {kinds: [MESSAGE], "#h": [room]}
|
const filter = {kinds: [MESSAGE], "#h": [room]}
|
||||||
const isFavorite = $derived($userRoomsByUrl.get(url)?.has(room))
|
const isFavorite = $derived($userRoomsByUrl.get(url)?.has(room))
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const membershipStatus = deriveUserMembershipStatus(url, room)
|
const membershipStatus = deriveUserMembershipStatus(url, room)
|
||||||
|
|
||||||
const addFavorite = () => addRoomMembership(url, room)
|
const addFavorite = () => addRoomMembership(url, room)
|
||||||
@@ -109,7 +111,7 @@
|
|||||||
const onSubmit = async ({content, tags}: EventContent) => {
|
const onSubmit = async ({content, tags}: EventContent) => {
|
||||||
tags.push(["h", room])
|
tags.push(["h", room])
|
||||||
|
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +125,20 @@
|
|||||||
template = prependParent(parent, template)
|
template = prependParent(parent, template)
|
||||||
}
|
}
|
||||||
|
|
||||||
publishThunk({
|
const thunk = publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
event: makeEvent(MESSAGE, template),
|
event: makeEvent(MESSAGE, template),
|
||||||
delay: $userSettingValues.send_delay,
|
delay: $userSettingValues.send_delay,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
clearParent()
|
clearParent()
|
||||||
clearShare()
|
clearShare()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,21 @@
|
|||||||
import PageBar from "@lib/components/PageBar.svelte"
|
import PageBar from "@lib/components/PageBar.svelte"
|
||||||
import PageContent from "@lib/components/PageContent.svelte"
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||||
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
||||||
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
||||||
import {userSettingValues, decodeRelay, getEventsForUrl} from "@app/state"
|
import {
|
||||||
import {setChecked, checked} from "@app/notifications"
|
userSettingValues,
|
||||||
|
decodeRelay,
|
||||||
|
getEventsForUrl,
|
||||||
|
PROTECTED,
|
||||||
|
REACTION_KINDS,
|
||||||
|
} from "@app/state"
|
||||||
import {prependParent, canEnforceNip70} from "@app/commands"
|
import {prependParent, canEnforceNip70} from "@app/commands"
|
||||||
import {PROTECTED, REACTION_KINDS} from "@app/state"
|
import {setChecked, checked} from "@app/notifications"
|
||||||
|
import {pushToast} from "@app/toast"
|
||||||
import {makeFeed} from "@app/requests"
|
import {makeFeed} from "@app/requests"
|
||||||
import {popKey} from "@app/implicit"
|
import {popKey} from "@app/implicit"
|
||||||
|
|
||||||
@@ -29,6 +36,7 @@
|
|||||||
const lastChecked = $checked[$page.url.pathname]
|
const lastChecked = $checked[$page.url.pathname]
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const filter = {kinds: [MESSAGE]}
|
const filter = {kinds: [MESSAGE]}
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const replyTo = (event: TrustedEvent) => {
|
const replyTo = (event: TrustedEvent) => {
|
||||||
parent = event
|
parent = event
|
||||||
@@ -44,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async ({content, tags}: EventContent) => {
|
const onSubmit = async ({content, tags}: EventContent) => {
|
||||||
if (await canEnforceNip70(url)) {
|
if (await shouldProtect) {
|
||||||
tags.push(PROTECTED)
|
tags.push(PROTECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +66,20 @@
|
|||||||
template = prependParent(parent, template)
|
template = prependParent(parent, template)
|
||||||
}
|
}
|
||||||
|
|
||||||
publishThunk({
|
const thunk = publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
event: makeEvent(MESSAGE, template),
|
event: makeEvent(MESSAGE, template),
|
||||||
delay: $userSettingValues.send_delay,
|
delay: $userSettingValues.send_delay,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
clearParent()
|
clearParent()
|
||||||
clearShare()
|
clearShare()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user