Avoid reflow by showing chat thunk status in a toast
This commit is contained in:
@@ -22,15 +22,17 @@
|
||||
showActivity?: boolean
|
||||
} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const path = makeCalendarPath(url, event.id)
|
||||
|
||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
const {url, header, initialValues}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const uploading = writable(false)
|
||||
|
||||
const back = () => history.back()
|
||||
@@ -75,7 +77,7 @@
|
||||
...ed.storage.nostr.getEditorTags(),
|
||||
]
|
||||
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.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 ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
|
||||
@@ -30,6 +30,7 @@
|
||||
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
||||
|
||||
const thunk = $thunks[event.id]
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const today = formatTimestampAsDate(now())
|
||||
const profile = deriveProfile(event.pubkey, [url])
|
||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||
@@ -42,10 +43,10 @@
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
</script>
|
||||
|
||||
<TapTarget
|
||||
@@ -79,7 +80,7 @@
|
||||
<div class="text-sm">
|
||||
<Content minimalQuote {event} {url} />
|
||||
{#if thunk}
|
||||
<ThunkStatus {thunk} class="mt-2" />
|
||||
<ThunkFailure showToastOnRetry {thunk} class="mt-2" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
|
||||
const {url, event} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const onEmoji = async (emoji: NativeEmoji) =>
|
||||
publishReaction({
|
||||
event,
|
||||
relays: [url],
|
||||
content: emoji.unicode,
|
||||
protect: await canEnforceNip70(url),
|
||||
protect: await shouldProtect,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
|
||||
const {url, event, reply}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const onEmoji = (async (event: TrustedEvent, url: string, emoji: NativeEmoji) => {
|
||||
history.back()
|
||||
publishReaction({
|
||||
event,
|
||||
relays: [url],
|
||||
content: emoji.unicode,
|
||||
protect: await canEnforceNip70(url),
|
||||
protect: await shouldProtect,
|
||||
})
|
||||
}).bind(undefined, event, url)
|
||||
|
||||
|
||||
@@ -26,9 +26,11 @@
|
||||
pubkey,
|
||||
tagPubkey,
|
||||
sendWrapped,
|
||||
mergeThunks,
|
||||
loadInboxRelaySelections,
|
||||
inboxRelaySelectionsByPubkey,
|
||||
} from "@welshman/app"
|
||||
import type {AbstractThunk} from "@welshman/app"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
@@ -44,6 +46,7 @@
|
||||
import ChatMessage from "@app/components/ChatMessage.svelte"
|
||||
import ChatCompose from "@app/components/ChatCompose.svelte"
|
||||
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import {
|
||||
INDEXER_RELAYS,
|
||||
userSettingValues,
|
||||
@@ -53,6 +56,7 @@
|
||||
} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {prependParent} from "@app/commands"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
type Props = {
|
||||
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
|
||||
// Sleep 1 second between each one to make sure timestamps are distinct
|
||||
const thunks: AbstractThunk[] = []
|
||||
for (let i = 0; i < templates.length; 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()
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
import Content from "@app/components/Content.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 ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
|
||||
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
|
||||
@@ -59,7 +59,7 @@
|
||||
</script>
|
||||
|
||||
{#if thunk}
|
||||
<ThunkStatus {thunk} class="mt-1" />
|
||||
<ThunkFailure showToastOnRetry {thunk} class="mt-1" />
|
||||
{/if}
|
||||
<div
|
||||
data-event={event.id}
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
|
||||
const {url, event, showActivity = false}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const path = makeThreadPath(url, event.id)
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
const {url, noun, event, hideZap, customActions}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const showPopover = () => popover?.show()
|
||||
|
||||
const hidePopover = () => popover?.hide()
|
||||
@@ -31,7 +33,7 @@
|
||||
event,
|
||||
content: emoji.unicode,
|
||||
relays: [url],
|
||||
protect: await canEnforceNip70(url),
|
||||
protect: await shouldProtect,
|
||||
})
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
|
||||
const {url, event}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const confirm = async () => {
|
||||
await publishDelete({event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
await publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||
|
||||
clearModals()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
const {url, event, onClose, onSubmit} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const uploading = writable(false)
|
||||
|
||||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
@@ -25,7 +27,7 @@
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
const tags = ed.storage.nostr.getEditorTags()
|
||||
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
const {url, event} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const reports = deriveEvents(repository, {
|
||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||
})
|
||||
@@ -17,7 +19,7 @@
|
||||
const back = () => history.back()
|
||||
|
||||
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) {
|
||||
history.back()
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
|
||||
const {url, event, showActivity = false}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const path = makeGoalPath(url, event.id)
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
const {url} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const uploading = writable(false)
|
||||
|
||||
const back = () => history.back()
|
||||
@@ -50,7 +52,7 @@
|
||||
["relays", url],
|
||||
]
|
||||
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,20 @@
|
||||
|
||||
const {url, event} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
|
||||
const onEmoji = async (emoji: NativeEmoji) =>
|
||||
publishReaction({
|
||||
event,
|
||||
content: emoji.unicode,
|
||||
relays: [url],
|
||||
protect: await canEnforceNip70(url),
|
||||
protect: await shouldProtect,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
|
||||
const {url, event, showActivity = false}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const path = makeThreadPath(url, event.id)
|
||||
|
||||
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) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
const {url} = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const uploading = writable(false)
|
||||
|
||||
const back = () => history.back()
|
||||
@@ -44,7 +46,7 @@
|
||||
|
||||
const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]]
|
||||
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
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">
|
||||
import {stopPropagation} from "svelte/legacy"
|
||||
import {nth, noop} from "@welshman/lib"
|
||||
import {PublishStatus} from "@welshman/net"
|
||||
import {
|
||||
MergedThunk,
|
||||
publishThunk,
|
||||
isMergedThunk,
|
||||
thunkIsComplete,
|
||||
thunkHasStatus,
|
||||
} from "@welshman/app"
|
||||
import {MergedThunk, 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 ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
|
||||
import {userSettingValues} from "@app/state"
|
||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||
import ThunkPending from "@app/components/ThunkPending.svelte"
|
||||
|
||||
interface Props {
|
||||
thunk: Thunk | MergedThunk
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {thunk, ...restProps}: Props = $props()
|
||||
const {thunk, ...restProps}: Props = $props()
|
||||
|
||||
const abort = () => thunk.controller.abort()
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
|
||||
const showPending = $derived(!thunkIsComplete($thunk))
|
||||
</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>
|
||||
<ThunkFailure class={restProps.class} {thunk} />
|
||||
{:else if showPending}
|
||||
<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={canCancel}
|
||||
class:opacity-25={!canCancel}
|
||||
onclick={stopPropagation(abort)}>
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<ThunkPending class={restProps.class} {thunk} />
|
||||
{/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:text-base-content={theme === "info"}
|
||||
class:alert-error={theme === "error"}>
|
||||
<p class="welshman-content-error">
|
||||
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||
{#if $toast.action}
|
||||
<Button class="cursor-pointer underline" onclick={onActionClick}>
|
||||
{$toast.action.message}
|
||||
</Button>
|
||||
<p class:welshman-content-error={theme === "error"}>
|
||||
{#if $toast.message}
|
||||
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||
{#if $toast.action}
|
||||
<Button class="cursor-pointer underline" onclick={onActionClick}>
|
||||
{$toast.action.message}
|
||||
</Button>
|
||||
{/if}
|
||||
{:else if $toast.children}
|
||||
{@const {component: Component, props} = $toast?.children || {}}
|
||||
<Component toast={$toast} {...props} />
|
||||
{/if}
|
||||
</p>
|
||||
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ import {
|
||||
userFollows,
|
||||
ensurePlaintext,
|
||||
thunks,
|
||||
walkThunks,
|
||||
flattenThunks,
|
||||
signer,
|
||||
makeOutboxLoader,
|
||||
appContext,
|
||||
@@ -260,7 +260,7 @@ export const getUrlsForEvent = derived([trackerStore, thunks], ([$tracker, $thun
|
||||
const getThunksByEventId = memoize(() => {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -1,11 +1,16 @@
|
||||
import type {Component} from "svelte"
|
||||
import {writable} from "svelte/store"
|
||||
import {randomId} from "@welshman/lib"
|
||||
import {copyToClipboard} from "@lib/html"
|
||||
|
||||
export type ToastParams = {
|
||||
message: string
|
||||
message?: string
|
||||
timeout?: number
|
||||
theme?: "error"
|
||||
children?: {
|
||||
component: Component<any>
|
||||
props: Record<string, any>
|
||||
}
|
||||
action?: {
|
||||
message: string
|
||||
onclick: () => void
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ChannelName from "@app/components/ChannelName.svelte"
|
||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||
@@ -57,6 +58,7 @@
|
||||
const channel = deriveChannel(url, room)
|
||||
const filter = {kinds: [MESSAGE], "#h": [room]}
|
||||
const isFavorite = $derived($userRoomsByUrl.get(url)?.has(room))
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const membershipStatus = deriveUserMembershipStatus(url, room)
|
||||
|
||||
const addFavorite = () => addRoomMembership(url, room)
|
||||
@@ -109,7 +111,7 @@
|
||||
const onSubmit = async ({content, tags}: EventContent) => {
|
||||
tags.push(["h", room])
|
||||
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
@@ -123,12 +125,20 @@
|
||||
template = prependParent(parent, template)
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
const thunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(MESSAGE, template),
|
||||
delay: $userSettingValues.send_delay,
|
||||
})
|
||||
|
||||
pushToast({
|
||||
timeout: 30_000,
|
||||
children: {
|
||||
component: ThunkToast,
|
||||
props: {thunk},
|
||||
},
|
||||
})
|
||||
|
||||
clearParent()
|
||||
clearShare()
|
||||
}
|
||||
|
||||
@@ -14,14 +14,21 @@
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ChannelMessage from "@app/components/ChannelMessage.svelte"
|
||||
import ChannelCompose from "@app/components/ChannelCompose.svelte"
|
||||
import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte"
|
||||
import {userSettingValues, decodeRelay, getEventsForUrl} from "@app/state"
|
||||
import {setChecked, checked} from "@app/notifications"
|
||||
import {
|
||||
userSettingValues,
|
||||
decodeRelay,
|
||||
getEventsForUrl,
|
||||
PROTECTED,
|
||||
REACTION_KINDS,
|
||||
} from "@app/state"
|
||||
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 {popKey} from "@app/implicit"
|
||||
|
||||
@@ -29,6 +36,7 @@
|
||||
const lastChecked = $checked[$page.url.pathname]
|
||||
const url = decodeRelay($page.params.relay)
|
||||
const filter = {kinds: [MESSAGE]}
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const replyTo = (event: TrustedEvent) => {
|
||||
parent = event
|
||||
@@ -44,7 +52,7 @@
|
||||
}
|
||||
|
||||
const onSubmit = async ({content, tags}: EventContent) => {
|
||||
if (await canEnforceNip70(url)) {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
@@ -58,12 +66,20 @@
|
||||
template = prependParent(parent, template)
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
const thunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(MESSAGE, template),
|
||||
delay: $userSettingValues.send_delay,
|
||||
})
|
||||
|
||||
pushToast({
|
||||
timeout: 30_000,
|
||||
children: {
|
||||
component: ThunkToast,
|
||||
props: {thunk},
|
||||
},
|
||||
})
|
||||
|
||||
clearParent()
|
||||
clearShare()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user