Add up/edit to chats

This commit is contained in:
Jon Staab
2026-03-10 15:13:33 -07:00
parent ffdd689331
commit cd54bc2880
14 changed files with 258 additions and 105 deletions
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
# Current
* Enable email/password login
* Add up/edit to direct messages
# 1.6.5 # 1.6.5
* Attempt to fix permission grant for notifications * Attempt to fix permission grant for notifications
+122 -65
View File
@@ -2,9 +2,11 @@
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {onMount} from "svelte" import {onMount} from "svelte"
import { import {
ago,
int, int,
ms, ms,
partition, partition,
ifLet,
spec, spec,
nthEq, nthEq,
nthNe, nthNe,
@@ -46,11 +48,12 @@
import ChatMembers from "@app/components/ChatMembers.svelte" import ChatMembers from "@app/components/ChatMembers.svelte"
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 ChatComposeEdit from "@app/components/ChatComposeEdit.svelte"
import ChatComposeParent from "@app/components/ChatComposeParent.svelte" import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
import ThunkToast from "@app/components/ThunkToast.svelte" import ThunkToast from "@app/components/ThunkToast.svelte"
import {userSettingsValues, PLATFORM_NAME, deriveChat} from "@app/core/state" import {userSettingsValues, PLATFORM_NAME, deriveChat} from "@app/core/state"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {prependParent} from "@app/core/commands" import {makeDelete, prependParent} from "@app/core/commands"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
type Props = { type Props = {
@@ -78,73 +81,115 @@
parent = undefined parent = undefined
} }
const onSubmit = async (params: EventContent) => { const clearEventToEdit = () => {
const ptags = remove($pubkey!, pubkeys).map(tagPubkey) eventToEdit = undefined
// Remove p tags since they result in forking the conversation
params.tags = params.tags.filter(nthNe(0, "p"))
// Add our reply quote to content
params = prependParent(parent, params)
const [imetaTags, tags] = partition(nthEq(0, "imeta"), params.tags)
const imetas = getTags("imeta", imetaTags).map(tagsFromIMeta)
const templates: EventTemplate[] = []
const buffer = []
const addTemplate = (kind: number, content: string, tags: string[][]) => {
content = content.trim()
if (content) {
templates.push(makeEvent(kind, {content, tags: [...tags, ...ptags]}))
}
}
for (const p of parse(params)) {
const imeta = isLink(p)
? imetas.find(tags => tags.find(spec(["url", p.value.url.toString()])))
: undefined
if (isLink(p) && imeta) {
addTemplate(DIRECT_MESSAGE, buffer.splice(0).join(""), tags)
addTemplate(
DIRECT_MESSAGE_FILE,
p.value.url.toString(),
imeta.slice(1).filter(nthNe(0, "url")),
)
} else {
buffer.push(p.raw)
}
}
addTemplate(DIRECT_MESSAGE, buffer.splice(0).join(""), tags)
// 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 = await Promise.all(
Array.from(enumerate(templates)).map(([i, event]) =>
sendWrapped({
event,
recipients: pubkeys,
delay: $userSettingsValues.send_delay + ms(i),
}),
),
)
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk: mergeThunks(thunks)},
},
})
clearParent()
} }
const onSubmit = async (params: EventContent) => {
try {
const ptags = remove($pubkey!, pubkeys).map(tagPubkey)
// Remove p tags since they result in forking the conversation
params.tags = params.tags.filter(nthNe(0, "p"))
// Add our reply quote to content
params = prependParent(parent, params)
if (eventToEdit) {
if (eventToEdit.content === params.content) {
return
}
await sendWrapped({
event: makeDelete({event: eventToEdit, protect: false}),
recipients: pubkeys,
})
}
const [imetaTags, tags] = partition(nthEq(0, "imeta"), params.tags)
const imetas = getTags("imeta", imetaTags).map(tagsFromIMeta)
const templates: EventTemplate[] = []
const buffer = []
const addTemplate = (kind: number, content: string, tags: string[][]) => {
content = content.trim()
if (content) {
templates.push(
makeEvent(kind, {
content,
tags: [...tags, ...ptags],
created_at: eventToEdit?.created_at,
}),
)
}
}
for (const p of parse(params)) {
const imeta = isLink(p)
? imetas.find(tags => tags.find(spec(["url", p.value.url.toString()])))
: undefined
if (isLink(p) && imeta) {
addTemplate(DIRECT_MESSAGE, buffer.splice(0).join(""), tags)
addTemplate(
DIRECT_MESSAGE_FILE,
p.value.url.toString(),
imeta.slice(1).filter(nthNe(0, "url")),
)
} else {
buffer.push(p.raw)
}
}
addTemplate(DIRECT_MESSAGE, buffer.splice(0).join(""), tags)
// 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 = await Promise.all(
Array.from(enumerate(templates)).map(([i, event]) =>
sendWrapped({
event,
recipients: pubkeys,
delay: $userSettingsValues.send_delay + ms(i),
}),
),
)
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk: mergeThunks(thunks)},
},
})
} finally {
clearParent()
clearEventToEdit()
}
}
const onEscape = () => {
clearParent()
clearEventToEdit()
}
const canEditEvent = (event: TrustedEvent) =>
event.pubkey === $pubkey &&
event.kind === DIRECT_MESSAGE &&
event.created_at >= ago(500, MINUTE)
const onEditEvent = (event: TrustedEvent) => {
clearParent()
eventToEdit = event
}
const onEditPrevious = () => ifLet($chat?.messages.toReversed().find(canEditEvent), onEditEvent)
let loading = $state(true) let loading = $state(true)
let compose: ChatCompose | undefined = $state() let compose: ChatCompose | undefined = $state()
let parent: TrustedEvent | undefined = $state() let parent: TrustedEvent | undefined = $state()
let eventToEdit: TrustedEvent | undefined = $state()
let chatCompose: HTMLElement | undefined = $state() let chatCompose: HTMLElement | undefined = $state()
let dynamicPadding: HTMLElement | undefined = $state() let dynamicPadding: HTMLElement | undefined = $state()
@@ -285,7 +330,9 @@
event={$state.snapshot(value as TrustedEvent)} event={$state.snapshot(value as TrustedEvent)}
{pubkeys} {pubkeys}
{showPubkey} {showPubkey}
{replyTo} /> {replyTo}
canEdit={canEditEvent}
onEdit={onEditEvent} />
{/if} {/if}
{/each} {/each}
<p class="m-auto flex h-10 max-w-sm flex-col items-center justify-center gap-4 py-20 text-center"> <p class="m-auto flex h-10 max-w-sm flex-col items-center justify-center gap-4 py-20 text-center">
@@ -305,6 +352,16 @@
{#if parent} {#if parent}
<ChatComposeParent event={parent} clear={clearParent} verb="Replying to" /> <ChatComposeParent event={parent} clear={clearParent} verb="Replying to" />
{/if} {/if}
{#if eventToEdit}
<ChatComposeEdit clear={clearEventToEdit} />
{/if}
</div> </div>
<ChatCompose bind:this={compose} {onSubmit} /> {#key eventToEdit}
<ChatCompose
bind:this={compose}
{onSubmit}
{onEscape}
{onEditPrevious}
content={eventToEdit?.content} />
{/key}
</div> </div>
+29 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import {onDestroy, onMount} from "svelte"
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {EventContent} from "@welshman/util" import type {EventContent} from "@welshman/util"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
@@ -10,10 +11,13 @@
import {makeEditor} from "@app/editor" import {makeEditor} from "@app/editor"
type Props = { type Props = {
content?: string
onEscape?: () => void
onEditPrevious?: () => void
onSubmit: (event: EventContent) => void onSubmit: (event: EventContent) => void
} }
const {onSubmit}: Props = $props() const {content, onEscape, onEditPrevious, onSubmit}: Props = $props()
const autofocus = !isMobile const autofocus = !isMobile
@@ -21,6 +25,19 @@
export const focus = () => editor.then(ed => ed.chain().focus().run()) export const focus = () => editor.then(ed => ed.chain().focus().run())
export const canEnterEditPrevious = () =>
editor.then(ed => ed.getText({blockSeparator: "\n"}) === "")
const handleKeyDown = async (event: KeyboardEvent) => {
if (event.key === "Escape") {
onEscape?.()
}
if (event.key === "ArrowUp" && (await canEnterEditPrevious())) {
onEditPrevious?.()
}
}
const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run()) const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run())
const submit = async () => { const submit = async () => {
@@ -38,12 +55,23 @@
} }
const editor = makeEditor({ const editor = makeEditor({
content,
autofocus, autofocus,
submit, submit,
uploading, uploading,
aggressive: true, aggressive: true,
encryptFiles: true, encryptFiles: true,
}) })
onMount(async () => {
const ed = await editor
ed.view.dom.addEventListener("keydown", handleKeyDown)
})
onDestroy(async () => {
const ed = await editor
ed?.view?.dom.removeEventListener("keydown", handleKeyDown)
})
</script> </script>
<form class="relative z-feature flex gap-2 p-2" onsubmit={preventDefault(submit)}> <form class="relative z-feature flex gap-2 p-2" onsubmit={preventDefault(submit)}>
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts">
import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
const {
clear,
}: {
clear: () => void
} = $props()
</script>
<div
class="relative flex h-8 items-center justify-between border-l-2 border-solid border-primary bg-base-300 px-2 pr-7 text-xs"
transition:slide>
<p class="text-primary">Editing message</p>
<Button onclick={clear} class="flex items-center">
<Icon icon={CloseCircle} />
</Button>
</div>
+7 -4
View File
@@ -23,11 +23,13 @@
interface Props { interface Props {
event: TrustedEvent event: TrustedEvent
replyTo: (event: TrustedEvent) => void replyTo: (event: TrustedEvent) => void
canEdit?: (event: TrustedEvent) => boolean
onEdit?: (event: TrustedEvent) => void
pubkeys: string[] pubkeys: string[]
showPubkey?: boolean showPubkey?: boolean
} }
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props() const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false}: Props = $props()
const isOwn = event.pubkey === $pubkey const isOwn = event.pubkey === $pubkey
const profileDisplay = deriveProfileDisplay(event.pubkey) const profileDisplay = deriveProfileDisplay(event.pubkey)
@@ -35,6 +37,7 @@
const [_, colorValue] = colors[hash(event.pubkey) % colors.length] const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const reply = () => replyTo(event) const reply = () => replyTo(event)
const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined
const deleteReaction = (event: TrustedEvent) => const deleteReaction = (event: TrustedEvent) =>
sendWrapped({event: makeDelete({event, protect: false}), recipients: pubkeys}) sendWrapped({event: makeDelete({event, protect: false}), recipients: pubkeys})
@@ -44,7 +47,7 @@
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey}) const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
const showMobileMenu = () => pushModal(ChatMessageMenuMobile, {event, pubkeys, reply}) const showMobileMenu = () => pushModal(ChatMessageMenuMobile, {event, pubkeys, reply, edit})
const togglePopover = () => { const togglePopover = () => {
if (popoverIsVisible) { if (popoverIsVisible) {
@@ -71,7 +74,7 @@
<Tippy <Tippy
bind:popover bind:popover
component={ChatMessageMenu} component={ChatMessageMenu}
props={{event, pubkeys, popover, replyTo}} props={{event, pubkeys, popover, replyTo, edit}}
params={{ params={{
interactive: true, interactive: true,
trigger: "manual", trigger: "manual",
@@ -93,7 +96,7 @@
{/if} {/if}
<div class="flex min-w-0 flex-col" class:items-end={isOwn}> <div class="flex min-w-0 flex-col" class:items-end={isOwn}>
<TapTarget <TapTarget
class="bg-alt chat-bubble mx-1 mb-2 flex cursor-auto flex-col gap-1 text-left lg:max-w-2xl" class="bg-alt chat-bubble mx-1 mb-2 flex cursor-auto flex-col gap-1 text-left lg:max-w-2xl min-w-[100px]"
onTap={showMobileMenu}> onTap={showMobileMenu}>
{#if showPubkey} {#if showPubkey}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
+8 -1
View File
@@ -4,12 +4,14 @@
import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte" import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte"
import EventInfo from "@app/components/EventInfo.svelte" import EventInfo from "@app/components/EventInfo.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
const {event, pubkeys, popover, replyTo} = $props() const {event, pubkeys, popover, replyTo, edit} = $props()
const reply = () => replyTo(event) const reply = () => replyTo(event)
const onEdit = () => edit?.()
const showInfo = () => { const showInfo = () => {
popover.hide() popover.hide()
@@ -24,6 +26,11 @@
<Icon size={4} icon={Reply} /> <Icon size={4} icon={Reply} />
</Button> </Button>
{/if} {/if}
{#if edit}
<Button class="btn join-item btn-xs" onclick={onEdit}>
<Icon size={4} icon={Pen} />
</Button>
{/if}
<Button class="btn join-item btn-xs" onclick={showInfo}> <Button class="btn join-item btn-xs" onclick={showInfo}>
<Icon size={4} icon={Code2} /> <Icon size={4} icon={Code2} />
</Button> </Button>
@@ -3,6 +3,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {sendWrapped} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl" import Copy from "@assets/icons/copy.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
@@ -20,9 +21,10 @@
pubkeys: string[] pubkeys: string[]
event: TrustedEvent event: TrustedEvent
reply: () => void reply: () => void
edit?: () => void
} }
const {event, pubkeys, reply}: Props = $props() const {event, pubkeys, reply, edit}: Props = $props()
const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => { const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => {
history.back() history.back()
@@ -39,6 +41,11 @@
reply() reply()
} }
const sendEdit = () => {
history.back()
edit?.()
}
const copyText = () => { const copyText = () => {
history.back() history.back()
clip(event.content) clip(event.content)
@@ -62,6 +69,12 @@
<Icon size={4} icon={Reply} /> <Icon size={4} icon={Reply} />
Send Reply Send Reply
</Button> </Button>
{#if edit}
<Button class="btn btn-neutral w-full" onclick={sendEdit}>
<Icon size={4} icon={Pen} />
Edit Message
</Button>
{/if}
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}> <Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
<Icon size={4} icon={SmileCircle} /> <Icon size={4} icon={SmileCircle} />
Send Reaction Send Reaction
+1 -1
View File
@@ -82,7 +82,7 @@
<p>Your recovery codes have been sent!</p> <p>Your recovery codes have been sent!</p>
<p> <p>
For security reasons, you may receive three or more emails with recovery codes in them. Please For security reasons, you may receive three or more emails with recovery codes in them. Please
paste <strong>all</strong> recovery codes into the text box below, on separate lines. paste <strong>all</strong> recovery codes into the text box below.
</p> </p>
<StringMultiInput bind:value={otps} placeholder="Enter your recovery codes..." /> <StringMultiInput bind:value={otps} placeholder="Enter your recovery codes..." />
</ModalBody> </ModalBody>
+1 -1
View File
@@ -86,7 +86,7 @@
<p>Your login codes have been sent!</p> <p>Your login codes have been sent!</p>
<p> <p>
For security reasons, you may receive three or more emails with login codes in them. Please For security reasons, you may receive three or more emails with login codes in them. Please
paste <strong>all</strong> login codes into the text box below, on separate lines. paste <strong>all</strong> login codes into the text box below.
</p> </p>
<StringMultiInput bind:value={otps} placeholder="Enter your login codes..." /> <StringMultiInput bind:value={otps} placeholder="Enter your login codes..." />
</ModalBody> </ModalBody>
+1 -1
View File
@@ -89,7 +89,7 @@
<p>Let's start by confirming your email.</p> <p>Let's start by confirming your email.</p>
<p> <p>
For security reasons, you may receive three or more emails with confirmation codes in them. For security reasons, you may receive three or more emails with confirmation codes in them.
Please paste <strong>all</strong> confirmation codes into the text box below, on separate lines. Please paste <strong>all</strong> confirmation codes into the text box below.
</p> </p>
<StringMultiInput bind:value={otps} placeholder="Enter your confirmation codes..." /> <StringMultiInput bind:value={otps} placeholder="Enter your confirmation codes..." />
</ModalBody> </ModalBody>
+44 -13
View File
@@ -14,6 +14,7 @@ import {
uniqBy, uniqBy,
sortBy, sortBy,
append, append,
reject,
sort, sort,
uniq, uniq,
indexBy, indexBy,
@@ -457,7 +458,7 @@ export const splitChatId = (id: string) => getChatPubkeys(id.split(","))
export const chatsById = call(() => { export const chatsById = call(() => {
const chatsById = new Map<string, Chat>() const chatsById = new Map<string, Chat>()
const chatsByPubkey = new Map<string, Chat[]>() const chatsByPubkey = new Map<string, string[]>()
const addSearchText = (chat: Override<Chat, {search_text?: string}>) => { const addSearchText = (chat: Override<Chat, {search_text?: string}>) => {
chat.search_text = chat.search_text =
@@ -469,6 +470,12 @@ export const chatsById = call(() => {
} }
return readable(chatsById, set => { return readable(chatsById, set => {
const indexChatByPubkeys = (chat: Chat) => {
for (const pubkey of chat.pubkeys) {
chatsByPubkey.set(pubkey, uniq(append(chat.id, chatsByPubkey.get(pubkey) || [])))
}
}
const addEvents = (events: TrustedEvent[]) => { const addEvents = (events: TrustedEvent[]) => {
let dirty = false let dirty = false
for (const event of events) { for (const event of events) {
@@ -484,21 +491,19 @@ export const chatsById = call(() => {
const updatedChat = addSearchText({id, pubkeys, messages, last_activity}) const updatedChat = addSearchText({id, pubkeys, messages, last_activity})
chatsById.set(id, updatedChat) chatsById.set(id, updatedChat)
indexChatByPubkeys(updatedChat)
for (const pubkey of pubkeys) {
const pubkeyChats = chatsByPubkey.get(pubkey) || []
const uniqueChats = uniqBy(chat => chat.id, append(updatedChat, pubkeyChats))
chatsByPubkey.set(pubkey, uniqueChats)
}
dirty = true dirty = true
} }
if (event.kind === PROFILE) { if (event.kind === PROFILE) {
for (const chat of chatsByPubkey.get(event.pubkey) || []) { for (const chatId of chatsByPubkey.get(event.pubkey) || []) {
addSearchText(chat) const chat = chatsById.get(chatId)
dirty = true
if (chat) {
addSearchText(chat)
dirty = true
}
} }
} }
} }
@@ -508,10 +513,36 @@ export const chatsById = call(() => {
} }
} }
addEvents(repository.query([{kinds: [...DM_KINDS, PROFILE]}])) const removeEvents = (removed: Set<string>) => {
let dirty = false
for (const id of removed) {
const event = repository.getEvent(id)
if (event && DM_KINDS.includes(event.kind)) {
for (const chatId of chatsByPubkey.get(event.pubkey) || []) {
const chat = chatsById.get(chatId)
if (chat) {
chat.messages = reject(spec({id: event.id}), chat.messages)
dirty = true
}
}
}
}
if (dirty) {
set(chatsById)
}
}
addEvents(repository.query([{kinds: [...DM_KINDS, DELETE, PROFILE]}]))
const unsubscribers = [ const unsubscribers = [
on(repository, "update", ({added}: RepositoryUpdate) => addEvents(added)), on(repository, "update", ({added, removed}: RepositoryUpdate) => {
addEvents(added)
removeEvents(removed)
}),
] ]
return () => unsubscribers.forEach(call) return () => unsubscribers.forEach(call)
+1 -1
View File
@@ -156,7 +156,7 @@
{/if} {/if}
<SignerStatus /> <SignerStatus />
{#if $session?.method === SessionMethod.Pomade} {#if $session?.method === SessionMethod.Pomade}
<div class="flex gap-2 justify-end"> <div class="flex flex-col lg:flex-row gap-4 lg:gap-2 justify-end">
<Button class="btn" onclick={startPasswordReset}> <Button class="btn" onclick={startPasswordReset}>
<Spinner {loading}>Update your password</Spinner> <Spinner {loading}>Update your password</Spinner>
</Button> </Button>
+2 -8
View File
@@ -5,7 +5,7 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app" import {pubkey, publishThunk, waitForThunkError, joinRoom, leaveRoom} from "@welshman/app"
import {now, int, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib" import {now, ifLet, int, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib"
import type {MakeNonOptional} from "@welshman/lib" import type {MakeNonOptional} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import { import {
@@ -336,13 +336,7 @@
eventToEdit = event eventToEdit = event
} }
const onEditPrevious = () => { const onEditPrevious = () => ifLet($events.toReversed().find(canEditEvent), onEditEvent)
const prev = $events.toReversed().find(e => e.pubkey === $pubkey)
if (prev && canEditEvent(prev)) {
onEditEvent(prev)
}
}
onMount(() => { onMount(() => {
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
+2 -8
View File
@@ -4,7 +4,7 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {readable} from "svelte/store" import {readable} from "svelte/store"
import {now, int, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib" import {now, int, ifLet, formatTimestampAsDate, MINUTE, ago} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {makeEvent, MESSAGE, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER} from "@welshman/util" import {makeEvent, MESSAGE, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER} from "@welshman/util"
import {pubkey, publishThunk} from "@welshman/app" import {pubkey, publishThunk} from "@welshman/app"
@@ -272,13 +272,7 @@
eventToEdit = event eventToEdit = event
} }
const onEditPrevious = () => { const onEditPrevious = () => ifLet($events.toReversed().find(canEditEvent), onEditEvent)
const prev = $events.toReversed().find(e => e.pubkey === $pubkey)
if (prev && canEditEvent(prev)) {
onEditEvent(prev)
}
}
onMount(() => { onMount(() => {
const controller = new AbortController() const controller = new AbortController()