Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd1b12b14a | |||
| c397ee38bb |
@@ -70,7 +70,6 @@ GoogleService-Info.plist
|
||||
.roo
|
||||
.idea/
|
||||
.vscode/
|
||||
.claude/
|
||||
|
||||
# OS generated
|
||||
.DS_Store
|
||||
|
||||
+5
-5
@@ -59,7 +59,7 @@
|
||||
"@getalby/lightning-tools": "^6.1.0",
|
||||
"@getalby/sdk": "^5.1.2",
|
||||
"@noble/curves": "^1.9.7",
|
||||
"@pomade/core": "^0.2.3",
|
||||
"@pomade/core": "^0.2.2",
|
||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@tiptap/core": "^2.27.2",
|
||||
@@ -101,9 +101,9 @@
|
||||
"onlyBuiltDependencies": [
|
||||
"sharp",
|
||||
"nostr-signer-capacitor-plugin"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"sharp": "0.35.0-rc.0"
|
||||
],
|
||||
"overrides": {
|
||||
"sharp": "0.35.0-rc.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+9
-9
@@ -60,8 +60,8 @@ importers:
|
||||
specifier: ^1.9.7
|
||||
version: 1.9.7
|
||||
'@pomade/core':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@poppanator/sveltekit-svg':
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(rollup@2.80.0)(svelte@5.48.0)(svgo@3.3.2)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0))
|
||||
@@ -85,7 +85,7 @@ importers:
|
||||
version: 0.6.8(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.48.0)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0)))(svelte@5.48.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.21(@types/node@25.0.10)(lightningcss@1.32.0)(terser@5.46.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
||||
'@welshman/app':
|
||||
specifier: ^0.8.12
|
||||
version: 0.8.12(2f5bd20a84c8c39e26176b5a5db083ae)
|
||||
version: 0.8.12(3074ef6691f94dc03952d8dbc98013a7)
|
||||
'@welshman/content':
|
||||
specifier: ^0.8.12
|
||||
version: 0.8.12(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
@@ -1427,9 +1427,9 @@ packages:
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@pomade/core@0.2.3':
|
||||
resolution: {integrity: sha512-36+abWfMH1Mif9FjBO7xICCkGZE4IqQpy7Csxlauyt0bhYQ9GsB07LqK5Qm3GgEHNwF9rFXdSZawkM+E6BeIfg==}
|
||||
version: 0.2.3
|
||||
'@pomade/core@0.2.2':
|
||||
resolution: {integrity: sha512-FoilLsO0gVjiKMW3LV63pmXU7x3gh8YVGVulyR6QJr4h47XrsBg8vPkZtKWr4+sH3sW31e2tNIPUb3ptiuhrMA==}
|
||||
version: 0.2.2
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@frostr/bifrost': ^1.0.7
|
||||
@@ -6610,7 +6610,7 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@pomade/core@0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
'@pomade/core@0.2.2(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@frostr/bifrost': 1.0.7(typescript@5.9.3)
|
||||
'@noble/hashes': 2.0.1
|
||||
@@ -7276,9 +7276,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@vite-pwa/assets-generator': 0.2.6
|
||||
|
||||
'@welshman/app@0.8.12(2f5bd20a84c8c39e26176b5a5db083ae)':
|
||||
'@welshman/app@0.8.12(3074ef6691f94dc03952d8dbc98013a7)':
|
||||
dependencies:
|
||||
'@pomade/core': 0.2.3(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@pomade/core': 0.2.2(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/signer@0.8.12(@noble/curves@1.9.7)(@noble/hashes@2.0.1)(@welshman/lib@0.8.12)(@welshman/net@0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1))(nostr-tools@2.20.0(typescript@5.9.3)))(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(nostr-tools@2.20.0(typescript@5.9.3))
|
||||
'@welshman/feeds': 0.8.12(d5b74f0c83250e052e0b96f7ff5804e8)
|
||||
'@welshman/lib': 0.8.12
|
||||
'@welshman/net': 0.8.12(@welshman/lib@0.8.12)(@welshman/util@0.8.12(@noble/curves@1.9.7)(@welshman/lib@0.8.12)(nostr-tools@2.20.0(typescript@5.9.3)))(ws@8.18.3)
|
||||
|
||||
+1
-1
@@ -425,7 +425,7 @@ body.keyboard-open .hide-on-keyboard {
|
||||
/* chat view */
|
||||
|
||||
.chat__compose {
|
||||
@apply z-compose relative mb-14 shrink-0 md:mb-0;
|
||||
@apply relative z-compose mb-14 shrink-0 md:mb-0;
|
||||
}
|
||||
|
||||
.chat__compose .chat__compose-inner {
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
const {url, h, shareToChat = false}: Props = $props()
|
||||
</script>
|
||||
|
||||
<CalendarEventForm {url} {h}>
|
||||
<CalendarEventForm {url} {h} {shareToChat}>
|
||||
{#snippet header()}
|
||||
<ModalHeader>
|
||||
<ModalTitle>Create an Event</ModalTitle>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {writable} from "svelte/store"
|
||||
import {randomId, HOUR} from "@welshman/lib"
|
||||
import {makeEvent, EVENT_TIME} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {publishThunk, waitForThunkError} from "@welshman/app"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||
@@ -22,7 +22,7 @@
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {canEnforceNip70} from "@app/core/commands"
|
||||
import {canEnforceNip70, publishRoomQuote} from "@app/core/commands"
|
||||
|
||||
type Values = {
|
||||
d: string
|
||||
@@ -36,11 +36,12 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
header: Snippet
|
||||
initialValues?: Values
|
||||
}
|
||||
|
||||
let {url, h, header, initialValues}: Props = $props()
|
||||
let {url, h, shareToChat = false, header, initialValues}: Props = $props()
|
||||
|
||||
const draftKey = new DraftKey<Values>(`calendar:${url}:${h ?? ""}`)
|
||||
|
||||
@@ -57,7 +58,7 @@
|
||||
const selectFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
if ($uploading || loading) return
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
@@ -92,22 +93,42 @@
|
||||
...ed.storage.nostr.getEditorTags(),
|
||||
]
|
||||
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const protect = await shouldProtect
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
const event = makeEvent(EVENT_TIME, {content, tags})
|
||||
const calendarThunk = publishThunk({event, relays: [url]})
|
||||
const error = await waitForThunkError(calendarThunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
|
||||
if (shareToChat) {
|
||||
publishRoomQuote({url, h, parent: calendarThunk.event, protect})
|
||||
}
|
||||
|
||||
pushToast({message: "Your event has been saved!"})
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
const event = makeEvent(EVENT_TIME, {content, tags})
|
||||
|
||||
pushToast({message: "Your event has been saved!"})
|
||||
publishThunk({event, relays: [url]})
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
|
||||
const d = $state(initialValues?.d ?? randomId())
|
||||
let title = $state(initialValues?.title ?? "")
|
||||
let location = $state(initialValues?.location ?? "")
|
||||
@@ -158,7 +179,11 @@
|
||||
<div class="input-editor grow overflow-hidden">
|
||||
<EditorContent {editor} />
|
||||
</div>
|
||||
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="center btn tooltip"
|
||||
onclick={selectFiles}
|
||||
disabled={loading}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
@@ -197,12 +222,12 @@
|
||||
</Field>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={$uploading}>
|
||||
<Spinner loading={$uploading}>Save Event</Spinner>
|
||||
<Button type="submit" class="btn btn-primary" disabled={$uploading || loading}>
|
||||
<Spinner loading={$uploading || loading}>Save Event</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
const {url, h, shareToChat = false}: Props = $props()
|
||||
</script>
|
||||
|
||||
<ClassifiedForm {url} {h}>
|
||||
<ClassifiedForm {url} {h} {shareToChat}>
|
||||
{#snippet header()}
|
||||
<ModalHeader>
|
||||
<ModalTitle>Create a Classified Listing</ModalTitle>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type {Snippet} from "svelte"
|
||||
import {removeUndefined, randomId, uniq} from "@welshman/lib"
|
||||
import {makeEvent, CLASSIFIED} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {publishThunk, waitForThunkError} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import {normalizeTopic} from "@lib/util"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
@@ -21,7 +21,7 @@
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import {canEnforceNip70, uploadFile} from "@app/core/commands"
|
||||
import {canEnforceNip70, publishRoomQuote, uploadFile} from "@app/core/commands"
|
||||
|
||||
type Values = {
|
||||
d: string
|
||||
@@ -37,11 +37,12 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
header: Snippet
|
||||
initialValues?: Values
|
||||
}
|
||||
|
||||
let {url, h, header, initialValues}: Props = $props()
|
||||
let {url, h, shareToChat = false, header, initialValues}: Props = $props()
|
||||
|
||||
const draftKey = new DraftKey<Values>(`classified:${url}:${h ?? ""}`)
|
||||
|
||||
@@ -87,7 +88,9 @@
|
||||
tags.push(["t", topic])
|
||||
}
|
||||
|
||||
if (await shouldProtect) {
|
||||
const protect = await shouldProtect
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
@@ -114,13 +117,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
const classifiedThunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(CLASSIFIED, {content, tags}),
|
||||
})
|
||||
|
||||
const error = await waitForThunkError(classifiedThunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
|
||||
if (shareToChat) {
|
||||
publishRoomQuote({url, h, parent: classifiedThunk.event, protect})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -22,15 +22,15 @@
|
||||
|
||||
const {url, h, onClick}: Props = $props()
|
||||
|
||||
const createGoal = () => pushModal(GoalCreate, {url, h})
|
||||
const createGoal = () => pushModal(GoalCreate, {url, h, shareToChat: true})
|
||||
|
||||
const createCalendarEvent = () => pushModal(CalendarEventCreate, {url, h})
|
||||
const createCalendarEvent = () => pushModal(CalendarEventCreate, {url, h, shareToChat: true})
|
||||
|
||||
const createThread = () => pushModal(ThreadCreate, {url, h})
|
||||
const createThread = () => pushModal(ThreadCreate, {url, h, shareToChat: true})
|
||||
|
||||
const createClassified = () => pushModal(ClassifiedCreate, {url, h})
|
||||
const createClassified = () => pushModal(ClassifiedCreate, {url, h, shareToChat: true})
|
||||
|
||||
const createPoll = () => pushModal(PollCreate, {url, h})
|
||||
const createPoll = () => pushModal(PollCreate, {url, h, shareToChat: true})
|
||||
|
||||
let ul: Element
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {writable} from "svelte/store"
|
||||
import {makeEvent, ZAP_GOAL} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {publishThunk, waitForThunkError} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
|
||||
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||
@@ -10,6 +10,7 @@
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
@@ -21,7 +22,7 @@
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import {canEnforceNip70} from "@app/core/commands"
|
||||
import {canEnforceNip70, publishRoomQuote} from "@app/core/commands"
|
||||
|
||||
type Values = {
|
||||
title: string
|
||||
@@ -33,9 +34,10 @@
|
||||
url: string
|
||||
h?: string
|
||||
initialValues?: Values
|
||||
shareToChat?: boolean
|
||||
}
|
||||
|
||||
let {url, h, initialValues}: Props = $props()
|
||||
let {url, h, initialValues, shareToChat = false}: Props = $props()
|
||||
|
||||
const draftKey = new DraftKey<Values>(`goal:${url}:${h ?? ""}`)
|
||||
|
||||
@@ -52,7 +54,7 @@
|
||||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
if ($uploading || loading) return
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
@@ -78,23 +80,43 @@
|
||||
["relays", url],
|
||||
]
|
||||
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const protect = await shouldProtect
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
const goalThunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(ZAP_GOAL, {content: title, tags}),
|
||||
})
|
||||
|
||||
const error = await waitForThunkError(goalThunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
|
||||
if (shareToChat) {
|
||||
publishRoomQuote({url, h, parent: goalThunk.event, protect})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(ZAP_GOAL, {content: title, tags}),
|
||||
})
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
|
||||
let title = $state(initialValues?.title ?? "")
|
||||
let amount = $state(initialValues?.amount ?? 1000)
|
||||
let content = $state(initialValues?.content ?? "")
|
||||
@@ -154,7 +176,8 @@
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||
onclick={selectFiles}>
|
||||
onclick={selectFiles}
|
||||
disabled={loading}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
@@ -188,10 +211,12 @@
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary">Create Goal</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={$uploading || loading}>
|
||||
<Spinner {loading}>Create Goal</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {insertAt, now, randomId, removeAt, removeUndefined} from "@welshman/lib"
|
||||
import {makeEvent} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {publishThunk, waitForThunkError} from "@welshman/app"
|
||||
import {Poll} from "nostr-tools/kinds"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
@@ -13,6 +13,7 @@
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
@@ -21,7 +22,7 @@
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {canEnforceNip70} from "@app/core/commands"
|
||||
import {canEnforceNip70, publishRoomQuote} from "@app/core/commands"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import type {PollType} from "@app/util/polls"
|
||||
|
||||
@@ -40,9 +41,10 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
const {url, h, shareToChat = false}: Props = $props()
|
||||
const draftKey = new DraftKey<Values>(`poll:${url}:${h ?? ""}`)
|
||||
const initialValues = draftKey.get()
|
||||
|
||||
@@ -102,6 +104,8 @@
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (loading) return
|
||||
|
||||
if (!title.trim()) {
|
||||
return pushToast({theme: "error", message: "Please provide a title for your poll."})
|
||||
}
|
||||
@@ -130,19 +134,39 @@
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const protect = await shouldProtect
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
const pollThunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(Poll, {content: title.trim(), tags}),
|
||||
})
|
||||
|
||||
const error = await waitForThunkError(pollThunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
|
||||
if (shareToChat) {
|
||||
publishRoomQuote({url, h, parent: pollThunk.event, protect})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(Poll, {content: title.trim(), tags}),
|
||||
})
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
|
||||
let draggedOptionId = $state<string | undefined>()
|
||||
let title = $state(initialValues?.title ?? "")
|
||||
let pollType = $state<PollType>(initialValues?.pollType ?? "singlechoice")
|
||||
@@ -246,10 +270,12 @@
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary">Create Poll</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Create Poll</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {deriveEventsDesc, deriveEventsById} from "@welshman/store"
|
||||
import {formatTimestampRelative} from "@welshman/lib"
|
||||
import {NOTE, ROOMS, COMMENT} from "@welshman/util"
|
||||
import {NOTE, ROOMS, COMMENT, MESSAGE} from "@welshman/util"
|
||||
import {repository, loadRelayList} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
|
||||
import {deriveGroupList, getSpaceUrlsFromGroupList, MESSAGE_KINDS} from "@app/core/state"
|
||||
import {deriveGroupList, getSpaceUrlsFromGroupList} from "@app/core/state"
|
||||
import {goToEvent} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
load({
|
||||
filters: [
|
||||
{authors: [pubkey], kinds: [ROOMS]},
|
||||
{authors: [pubkey], limit: 1, kinds: [NOTE, COMMENT, ...MESSAGE_KINDS]},
|
||||
{authors: [pubkey], limit: 1, kinds: [NOTE, COMMENT, MESSAGE]},
|
||||
],
|
||||
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
|
||||
})
|
||||
|
||||
@@ -8,22 +8,16 @@
|
||||
import {getRoomItemPath} from "@app/util/routes"
|
||||
|
||||
const props: ComponentProps<typeof NoteContent> = $props()
|
||||
const MESSAGE_MIN_LENGTH = 5000
|
||||
const MESSAGE_MAX_LENGTH = 5500
|
||||
|
||||
const path = getRoomItemPath(props.url!, props.event)
|
||||
const minLength =
|
||||
props.minLength ?? (props.event.kind === MESSAGE ? MESSAGE_MIN_LENGTH : undefined)
|
||||
const maxLength =
|
||||
props.maxLength ?? (props.event.kind === MESSAGE ? MESSAGE_MAX_LENGTH : undefined)
|
||||
</script>
|
||||
|
||||
<div class={cx("text-sm", {"card2 card2-sm bg-alt": props.event.kind !== MESSAGE})}>
|
||||
{#if path && !isMobile}
|
||||
<Link href={path}>
|
||||
<NoteContent {...props} {minLength} {maxLength} />
|
||||
<NoteContent {...props} />
|
||||
</Link>
|
||||
{:else}
|
||||
<NoteContent {...props} {minLength} {maxLength} />
|
||||
<NoteContent {...props} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import {writable} from "svelte/store"
|
||||
import {makeEvent, THREAD} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {publishThunk, waitForThunkError} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
@@ -19,7 +20,7 @@
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {DraftKey} from "@app/util/drafts"
|
||||
import {canEnforceNip70} from "@app/core/commands"
|
||||
import {canEnforceNip70, publishRoomQuote} from "@app/core/commands"
|
||||
|
||||
type Values = {
|
||||
content?: string | object
|
||||
@@ -29,9 +30,10 @@
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
shareToChat?: boolean
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
const {url, h, shareToChat = false}: Props = $props()
|
||||
const draftKey = new DraftKey<Values>(`thread:${url}:${h ?? ""}`)
|
||||
const initialValues = draftKey.get()
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
@@ -43,7 +45,7 @@
|
||||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
if ($uploading || loading) return
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
@@ -64,23 +66,43 @@
|
||||
|
||||
const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]]
|
||||
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const protect = await shouldProtect
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
const threadThunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(THREAD, {content, tags}),
|
||||
})
|
||||
|
||||
const error = await waitForThunkError(threadThunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error})
|
||||
}
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
|
||||
if (shareToChat) {
|
||||
publishRoomQuote({url, h, parent: threadThunk.event, protect})
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(THREAD, {content, tags}),
|
||||
})
|
||||
|
||||
draftKey.clear()
|
||||
history.back()
|
||||
}
|
||||
|
||||
let loading = $state(false)
|
||||
|
||||
let title = $state(initialValues?.title ?? "")
|
||||
let content = $state(initialValues?.content ?? "")
|
||||
|
||||
@@ -138,7 +160,8 @@
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||
onclick={selectFiles}>
|
||||
onclick={selectFiles}
|
||||
disabled={loading}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
@@ -148,10 +171,12 @@
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary">Create Thread</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={$uploading || loading}>
|
||||
<Spinner {loading}>Create Thread</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {PollResponse} from "nostr-tools/kinds"
|
||||
import {
|
||||
DELETE,
|
||||
REPORT,
|
||||
MESSAGE,
|
||||
PROFILE,
|
||||
MESSAGING_RELAYS,
|
||||
RELAYS,
|
||||
@@ -122,6 +123,34 @@ export const prependParent = (
|
||||
return {content, tags}
|
||||
}
|
||||
|
||||
export const publishRoomQuote = ({
|
||||
url,
|
||||
h,
|
||||
parent,
|
||||
protect,
|
||||
delay,
|
||||
}: {
|
||||
url: string
|
||||
h?: string
|
||||
parent: TrustedEvent
|
||||
protect: boolean
|
||||
delay?: number
|
||||
}) => {
|
||||
const tags: string[][] = []
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
if (protect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
const event = makeEvent(MESSAGE, prependParent(parent, {content: "", tags}, url))
|
||||
|
||||
return publishThunk({relays: [url], event, delay})
|
||||
}
|
||||
|
||||
// Synchronization
|
||||
|
||||
export const broadcastUserData = async (relays: string[]) => {
|
||||
|
||||
+65
-102
@@ -1,6 +1,5 @@
|
||||
import {writable} from "svelte/store"
|
||||
import {get, writable} from "svelte/store"
|
||||
import {
|
||||
batch,
|
||||
call,
|
||||
uniq,
|
||||
int,
|
||||
@@ -26,8 +25,7 @@ import {
|
||||
sortEventsDesc,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, Filter, List} from "@welshman/util"
|
||||
import {load, request, mergeRepositoryUpdates} from "@welshman/net"
|
||||
import type {RepositoryUpdate} from "@welshman/net"
|
||||
import {load, request} from "@welshman/net"
|
||||
import {repository, loadRelay, tracker} from "@welshman/app"
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
@@ -58,75 +56,57 @@ export const makeFeed = ({
|
||||
let backwardWindow = [at - interval, at]
|
||||
let forwardWindow = [at, at + interval]
|
||||
|
||||
const insertIntoBuffer = (event: TrustedEvent) => {
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
if (buffer[i].created_at > event.created_at) {
|
||||
buffer.splice(i, 0, event)
|
||||
return
|
||||
}
|
||||
}
|
||||
buffer.push(event)
|
||||
}
|
||||
const insertEvent = (event: TrustedEvent) => {
|
||||
let handled = false
|
||||
|
||||
// Batch-insert events into the visible store with a single update
|
||||
const insertEvents = (newEvents: TrustedEvent[]) => {
|
||||
const visible: TrustedEvent[] = []
|
||||
if (between([backwardWindow[0], forwardWindow[1]], event.created_at)) {
|
||||
const $events = get(events)
|
||||
|
||||
for (const event of newEvents) {
|
||||
if (between([backwardWindow[0], forwardWindow[1]], event.created_at)) {
|
||||
visible.push(event)
|
||||
} else {
|
||||
insertIntoBuffer(event)
|
||||
}
|
||||
}
|
||||
|
||||
if (visible.length > 0) {
|
||||
events.update($events => {
|
||||
for (const event of visible) {
|
||||
let inserted = false
|
||||
for (let i = 0; i < $events.length; i++) {
|
||||
if ($events[i].created_at > event.created_at) {
|
||||
$events = insertAt(i, event, $events)
|
||||
inserted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!inserted) {
|
||||
$events = [...$events, event]
|
||||
}
|
||||
for (let i = 0; i < $events.length; i++) {
|
||||
if ($events[i].created_at > event.created_at) {
|
||||
events.set(insertAt(i, event, $events))
|
||||
handled = true
|
||||
break
|
||||
}
|
||||
return $events
|
||||
})
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
events.set([...$events, event])
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
if (buffer[i].created_at > event.created_at) {
|
||||
buffer.splice(i, 0, event)
|
||||
handled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
buffer.push(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unsubscribers = [
|
||||
on(
|
||||
repository,
|
||||
"update",
|
||||
batch(150, (updates: RepositoryUpdate[]) => {
|
||||
const {added, removed} = mergeRepositoryUpdates(updates)
|
||||
on(repository, "update", ({added, removed}) => {
|
||||
if (removed.size > 0) {
|
||||
buffer = buffer.filter(e => !removed.has(e.id))
|
||||
events.update($events => $events.filter(e => !removed.has(e.id)))
|
||||
}
|
||||
|
||||
if (removed.size > 0) {
|
||||
buffer = buffer.filter(e => !removed.has(e.id))
|
||||
events.update($events => $events.filter(e => !removed.has(e.id)))
|
||||
for (const event of added) {
|
||||
if (matchFilters(filters, event) && tracker.getRelays(event.id).has(url)) {
|
||||
insertEvent(event)
|
||||
}
|
||||
|
||||
const matching = added.filter(
|
||||
event => matchFilters(filters, event) && tracker.getRelays(event.id).has(url),
|
||||
)
|
||||
|
||||
if (matching.length > 0) {
|
||||
insertEvents(matching)
|
||||
}
|
||||
}),
|
||||
),
|
||||
}
|
||||
}),
|
||||
on(tracker, "add", (id: string, trackerUrl: string) => {
|
||||
if (trackerUrl === url) {
|
||||
const event = repository.getEvent(id)
|
||||
|
||||
if (event && matchFilters(filters, event)) {
|
||||
insertEvents([event])
|
||||
insertEvent(event)
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -157,7 +137,9 @@ export const makeFeed = ({
|
||||
|
||||
backwardWindow = [since - interval, since]
|
||||
|
||||
insertEvents(buffer.splice(0, 30))
|
||||
for (const event of buffer.splice(0, 30)) {
|
||||
insertEvent(event)
|
||||
}
|
||||
|
||||
if (until > now() - int(2, YEAR)) {
|
||||
loadTimeframe(since, until)
|
||||
@@ -178,7 +160,9 @@ export const makeFeed = ({
|
||||
|
||||
forwardWindow = [until, until + interval]
|
||||
|
||||
insertEvents(buffer.splice(0, 30))
|
||||
for (const event of buffer.splice(0, 30)) {
|
||||
insertEvent(event)
|
||||
}
|
||||
|
||||
if (until < now()) {
|
||||
loadTimeframe(since, until)
|
||||
@@ -224,61 +208,40 @@ export const makeCalendarFeed = ({
|
||||
|
||||
const events = writable(sortBy(getStart, getEventsForUrl(url, filters)))
|
||||
|
||||
// Batch-insert calendar events into the store with a single update
|
||||
const insertEvents = (newEvents: TrustedEvent[]) => {
|
||||
const valid = newEvents.filter(e => !isNaN(getStart(e)) && !isNaN(getEnd(e)))
|
||||
if (valid.length === 0) return
|
||||
const insertEvent = (event: TrustedEvent) => {
|
||||
const start = getStart(event)
|
||||
const address = getAddress(event)
|
||||
|
||||
if (isNaN(start) || isNaN(getEnd(event))) return
|
||||
|
||||
events.update($events => {
|
||||
for (const event of valid) {
|
||||
const start = getStart(event)
|
||||
const address = getAddress(event)
|
||||
|
||||
let handled = false
|
||||
for (let i = 0; i < $events.length; i++) {
|
||||
if ($events[i].id === event.id) {
|
||||
handled = true
|
||||
break
|
||||
}
|
||||
if (getStart($events[i]) > start) {
|
||||
$events = insertAt(i, event, $events)
|
||||
handled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
$events = [...$events.filter(e => getAddress(e) !== address), event]
|
||||
}
|
||||
for (let i = 0; i < $events.length; i++) {
|
||||
if ($events[i].id === event.id) return $events
|
||||
if (getStart($events[i]) > start) return insertAt(i, event, $events)
|
||||
}
|
||||
return $events
|
||||
|
||||
return [...$events.filter(e => getAddress(e) !== address), event]
|
||||
})
|
||||
}
|
||||
|
||||
const unsubscribers = [
|
||||
on(
|
||||
repository,
|
||||
"update",
|
||||
batch(150, (updates: RepositoryUpdate[]) => {
|
||||
const {added, removed} = mergeRepositoryUpdates(updates)
|
||||
on(repository, "update", ({added, removed}) => {
|
||||
if (removed.size > 0) {
|
||||
events.update($events => $events.filter(e => !removed.has(e.id)))
|
||||
}
|
||||
|
||||
if (removed.size > 0) {
|
||||
events.update($events => $events.filter(e => !removed.has(e.id)))
|
||||
for (const event of added) {
|
||||
if (matchFilters(filters, event)) {
|
||||
insertEvent(event)
|
||||
}
|
||||
|
||||
const matching = added.filter(event => matchFilters(filters, event))
|
||||
|
||||
if (matching.length > 0) {
|
||||
insertEvents(matching)
|
||||
}
|
||||
}),
|
||||
),
|
||||
}
|
||||
}),
|
||||
on(tracker, "add", (id: string, trackerUrl: string) => {
|
||||
if (trackerUrl === url) {
|
||||
const event = repository.getEvent(id)
|
||||
|
||||
if (event && matchFilters(filters, event)) {
|
||||
insertEvents([event])
|
||||
insertEvent(event)
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
+22
-33
@@ -329,8 +329,6 @@ if (ENABLE_ZAPS) {
|
||||
|
||||
export const CONTENT_KINDS = [ZAP_GOAL, EVENT_TIME, THREAD, CLASSIFIED, Poll]
|
||||
|
||||
export const MESSAGE_KINDS = [...CONTENT_KINDS, MESSAGE]
|
||||
|
||||
export const DM_KINDS = [DIRECT_MESSAGE, DIRECT_MESSAGE_FILE]
|
||||
|
||||
// Settings
|
||||
@@ -554,7 +552,7 @@ export const chatsById = call(() => {
|
||||
setTimeout(() => {
|
||||
addEvents(added)
|
||||
removeEvents(removed)
|
||||
}, 200)
|
||||
}, 50)
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -568,7 +566,7 @@ export const deriveChat = call(() => {
|
||||
return (pubkeys: string[]) => _deriveChat(makeChatId(pubkeys))
|
||||
})
|
||||
|
||||
export const chatSearch = derived(throttled(1500, chatsById), $chatsByPubkey => {
|
||||
export const chatSearch = derived(throttled(800, chatsById), $chatsByPubkey => {
|
||||
return createSearch(
|
||||
sortBy(c => -c.last_activity, Array.from($chatsByPubkey.values())),
|
||||
{
|
||||
@@ -607,7 +605,7 @@ export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
|
||||
})
|
||||
|
||||
export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByUrl => {
|
||||
const result = new Map<string, Room[]>()
|
||||
const metaByIdByUrl = new Map<string, Map<string, Room>>()
|
||||
|
||||
for (const [url, events] of roomMetaEventsByIdByUrl.entries()) {
|
||||
const [metaEvents, deleteEvents] = partition(spec({kind: ROOM_META}), events.values())
|
||||
@@ -619,8 +617,6 @@ export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByU
|
||||
}
|
||||
}
|
||||
|
||||
const metaById = new Map<string, Room>()
|
||||
|
||||
for (const event of metaEvents) {
|
||||
const meta = tryCatch(() => readRoomMeta(event))
|
||||
|
||||
@@ -628,14 +624,22 @@ export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByU
|
||||
continue
|
||||
}
|
||||
|
||||
let metaById = metaByIdByUrl.get(url)
|
||||
if (!metaById) {
|
||||
metaById = new Map()
|
||||
metaByIdByUrl.set(url, metaById)
|
||||
}
|
||||
|
||||
const id = makeRoomId(url, meta.h)
|
||||
|
||||
metaById.set(id, {...meta, url, id})
|
||||
}
|
||||
}
|
||||
|
||||
if (metaById.size > 0) {
|
||||
result.set(url, Array.from(metaById.values()))
|
||||
}
|
||||
const result = new Map<string, Room[]>()
|
||||
|
||||
for (const [url, metaById] of metaByIdByUrl.entries()) {
|
||||
result.set(url, Array.from(metaById.values()))
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -943,33 +947,18 @@ export const deriveSpaceActionItems = (url: string) =>
|
||||
for (const [h, roomEvents] of groupBy(getRoomId, $events)) {
|
||||
if (!h) continue
|
||||
|
||||
const roomJoins: TrustedEvent[] = []
|
||||
const roomLeaves: TrustedEvent[] = []
|
||||
const roomMembershipEvents: TrustedEvent[] = []
|
||||
|
||||
for (const event of roomEvents) {
|
||||
switch (event.kind) {
|
||||
case ROOM_JOIN:
|
||||
roomJoins.push(event)
|
||||
break
|
||||
case ROOM_LEAVE:
|
||||
roomLeaves.push(event)
|
||||
break
|
||||
case ROOM_MEMBERS:
|
||||
case ROOM_ADD_MEMBER:
|
||||
case ROOM_REMOVE_MEMBER:
|
||||
roomMembershipEvents.push(event)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const roomJoins = roomEvents.filter(spec({kind: ROOM_JOIN}))
|
||||
const roomLeaves = roomEvents.filter(spec({kind: ROOM_LEAVE}))
|
||||
const roomMembershipEvents = roomEvents.filter(event =>
|
||||
[ROOM_MEMBERS, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER].includes(event.kind),
|
||||
)
|
||||
const roomMembers = new Set(getRoomMembers(url, h, roomMembershipEvents))
|
||||
|
||||
pendingJoins.push(
|
||||
...removeUndefined(
|
||||
Array.from(groupBy(e => e.pubkey, roomJoins).values()).map(events =>
|
||||
first(sortEventsDesc(events)),
|
||||
),
|
||||
Array.from(groupBy(e => e.pubkey, roomJoins).values())
|
||||
.map(sortEventsDesc)
|
||||
.map(first),
|
||||
).filter(({pubkey, created_at}) => {
|
||||
if (roomMembers.has(pubkey)) return false
|
||||
if (
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
RELAY_MEMBERS,
|
||||
RELAY_ADD_MEMBER,
|
||||
RELAY_REMOVE_MEMBER,
|
||||
MESSAGE,
|
||||
isSignedEvent,
|
||||
unionFilters,
|
||||
getTagValue,
|
||||
@@ -43,7 +44,6 @@ import {
|
||||
} from "@welshman/app"
|
||||
import {
|
||||
REACTION_KINDS,
|
||||
MESSAGE_KINDS,
|
||||
CONTENT_KINDS,
|
||||
INDEXER_RELAYS,
|
||||
loadSettings,
|
||||
@@ -281,7 +281,7 @@ const syncSpace = (url: string, rooms: string[]) => {
|
||||
signal: controller.signal,
|
||||
filters: [
|
||||
{kinds: [ROOM_META, ROOM_ADMINS, ROOM_MEMBERS], "#d": [room]},
|
||||
{kinds: MESSAGE_KINDS, since, "#h": [room]},
|
||||
{kinds: [MESSAGE], since, "#h": [room]},
|
||||
makeCommentFilter(CONTENT_KINDS, {since, "#h": [room]}),
|
||||
{
|
||||
kinds: [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER],
|
||||
@@ -305,7 +305,7 @@ const syncSpace = (url: string, rooms: string[]) => {
|
||||
url,
|
||||
signal: controller.signal,
|
||||
filters: [
|
||||
{kinds: [...relayKinds, ...roomMetaKinds, ...roomMemberKinds, ...MESSAGE_KINDS]},
|
||||
{kinds: [...relayKinds, ...roomMetaKinds, ...roomMemberKinds, MESSAGE]},
|
||||
makeCommentFilter(CONTENT_KINDS, {since}),
|
||||
{kinds: [PollResponse], since},
|
||||
],
|
||||
|
||||
@@ -5,10 +5,9 @@ import {pubkey, tracker, repository, relaysByUrl} from "@welshman/app"
|
||||
import {assoc, prop, first, identity, groupBy, now} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {deriveEventsByIdByUrl} from "@welshman/store"
|
||||
import {sortEventsDesc, getTagValue} from "@welshman/util"
|
||||
import {sortEventsDesc, getTagValue, MESSAGE} from "@welshman/util"
|
||||
import {makeSpacePath, makeRoomPath, makeSpaceChatPath, makeChatPath} from "@app/util/routes"
|
||||
import {
|
||||
MESSAGE_KINDS,
|
||||
notificationSettings,
|
||||
chatsById,
|
||||
userGroupList,
|
||||
@@ -85,7 +84,7 @@ export const allNotifications = derived(
|
||||
deriveEventsByIdByUrl({
|
||||
tracker,
|
||||
repository,
|
||||
filters: [{kinds: MESSAGE_KINDS}, makeCommentFilter(MESSAGE_KINDS)],
|
||||
filters: [{kinds: [MESSAGE]}, makeCommentFilter([MESSAGE])],
|
||||
}),
|
||||
],
|
||||
identity,
|
||||
|
||||
@@ -17,13 +17,12 @@ import {
|
||||
getRelaysFromList,
|
||||
getTagValue,
|
||||
matchFilters,
|
||||
MESSAGE,
|
||||
type Filter,
|
||||
type TrustedEvent,
|
||||
} from "@welshman/util"
|
||||
import {
|
||||
DM_KINDS,
|
||||
CONTENT_KINDS,
|
||||
MESSAGE_KINDS,
|
||||
notificationSettings,
|
||||
pushState,
|
||||
shouldNotify,
|
||||
@@ -45,7 +44,7 @@ export type PushPermissionResult = {
|
||||
}
|
||||
|
||||
export const onNotification = call(() => {
|
||||
const allFilters = [{kinds: [...MESSAGE_KINDS, ...DM_KINDS]}, makeCommentFilter(MESSAGE_KINDS)]
|
||||
const allFilters = [{kinds: [MESSAGE, ...DM_KINDS]}, makeCommentFilter([MESSAGE])]
|
||||
const filters = allFilters.map(assoc("since", now()))
|
||||
const subscribers: Subscriber<TrustedEvent>[] = []
|
||||
|
||||
@@ -158,7 +157,7 @@ export const syncRelaySubscriptions = (
|
||||
userSettingsValues,
|
||||
]).subscribe(
|
||||
throttle(3000, ([$userSpaceUrls, {spaces, mentions}, {alerts}]) => {
|
||||
const baseFilters = [{kinds: MESSAGE_KINDS}, makeCommentFilter(CONTENT_KINDS)]
|
||||
const baseFilters = [{kinds: [MESSAGE]}, makeCommentFilter([MESSAGE])]
|
||||
|
||||
for (const url of $userSpaceUrls) {
|
||||
const {notify = true, exceptions = []} = alerts.find(spec({url})) || {}
|
||||
|
||||
+4
-17
@@ -48,18 +48,6 @@ import {
|
||||
import type {Unsubscriber} from "svelte/store"
|
||||
import {db} from "@app/core/storage"
|
||||
|
||||
// Shared interval for all non-critical store flushes, so they batch on the same cadence
|
||||
const FLUSH_INTERVAL = 3000
|
||||
|
||||
// Wraps a write callback to run during idle time (non-critical persistence)
|
||||
const idleWrite = <T>(f: (xs: T[]) => void): ((xs: T[]) => void) => {
|
||||
if (typeof requestIdleCallback !== "undefined") {
|
||||
return (xs: T[]) => requestIdleCallback(() => f(xs))
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
const kinds = {
|
||||
meta: [PROFILE, FOLLOWS, MUTES, RELAYS, BLOSSOM_SERVERS, MESSAGING_RELAYS, APP_DATA, ROOMS],
|
||||
alert: [ALERT_STATUS, ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID],
|
||||
@@ -211,15 +199,14 @@ const loadCriticalRelays = async () => {
|
||||
relaysByUrl.set(indexBy(r => r.url, await table.getAll()))
|
||||
}
|
||||
|
||||
const syncRelays = () =>
|
||||
onRelay(batch(FLUSH_INTERVAL, idleWrite(db.table<RelayProfile>("relays").bulkPut)))
|
||||
const syncRelays = () => onRelay(batch(1000, db.table<RelayProfile>("relays").bulkPut))
|
||||
|
||||
const initRelayStats = async () => {
|
||||
const table = db.table<RelayStats>("relayStats")
|
||||
|
||||
relayStatsByUrl.set(indexBy(r => r.url, await table.getAll()))
|
||||
|
||||
return onRelayStats(batch(FLUSH_INTERVAL, idleWrite(table.bulkPut)))
|
||||
return onRelayStats(batch(1000, table.bulkPut))
|
||||
}
|
||||
|
||||
const initHandles = async () => {
|
||||
@@ -227,7 +214,7 @@ const initHandles = async () => {
|
||||
|
||||
handlesByNip05.set(indexBy(r => r.nip05, await table.getAll()))
|
||||
|
||||
return onHandle(batch(FLUSH_INTERVAL, idleWrite(table.bulkPut)))
|
||||
return onHandle(batch(1000, table.bulkPut))
|
||||
}
|
||||
|
||||
const initZappers = async () => {
|
||||
@@ -235,7 +222,7 @@ const initZappers = async () => {
|
||||
|
||||
zappersByLnurl.set(indexBy(z => z.lnurl, await table.getAll()))
|
||||
|
||||
return onZapper(batch(FLUSH_INTERVAL, idleWrite(table.bulkPut)))
|
||||
return onZapper(batch(3000, table.bulkPut))
|
||||
}
|
||||
|
||||
const initPlaintext = async () => {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
|
||||
let {
|
||||
children,
|
||||
root,
|
||||
initiallyVisible = false,
|
||||
estimatedHeight = 48,
|
||||
}: {
|
||||
children: Snippet
|
||||
root?: HTMLElement
|
||||
initiallyVisible?: boolean
|
||||
estimatedHeight?: number
|
||||
} = $props()
|
||||
|
||||
let visible = $state(initiallyVisible)
|
||||
let height = $state(estimatedHeight)
|
||||
let el: HTMLElement | undefined = $state()
|
||||
let hasMeasured = false
|
||||
|
||||
$effect(() => {
|
||||
if (!el) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true
|
||||
} else {
|
||||
// Measure actual height before hiding content
|
||||
if (el) {
|
||||
const h = el.offsetHeight
|
||||
if (h > 0) {
|
||||
height = h
|
||||
hasMeasured = true
|
||||
}
|
||||
}
|
||||
if (hasMeasured) {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
},
|
||||
{root: root || null, rootMargin: "1000px 0px"},
|
||||
)
|
||||
|
||||
observer.observe(el)
|
||||
return () => observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={el}>
|
||||
{#if visible}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<div style:height="{height}px"></div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -30,7 +30,6 @@
|
||||
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||
import VirtualItem from "@lib/components/VirtualItem.svelte"
|
||||
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
||||
import {canEnforceNip70, prependParent, publishDelete} from "@app/core/commands"
|
||||
import {
|
||||
@@ -38,7 +37,6 @@
|
||||
deriveRoom,
|
||||
deriveUserRoomMembershipStatus,
|
||||
getRoomType,
|
||||
MESSAGE_KINDS,
|
||||
MembershipStatus,
|
||||
PROTECTED,
|
||||
RoomType,
|
||||
@@ -106,7 +104,6 @@
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
|
||||
const at = $derived(parseInt($page.url.searchParams.get("at")!))
|
||||
const shouldVirtualize = $derived(isNaN(at))
|
||||
|
||||
const showRoomDetail = () => pushModal(RoomDetail, {url, h})
|
||||
|
||||
@@ -362,7 +359,7 @@
|
||||
url,
|
||||
at: at || now(),
|
||||
element: element!,
|
||||
filters: [{kinds: [...MESSAGE_KINDS, ROOM_ADD_MEMBER], "#h": [h]}],
|
||||
filters: [{kinds: [MESSAGE, ROOM_ADD_MEMBER], "#h": [h]}],
|
||||
onBackwardExhausted: () => {
|
||||
loadingBackward = false
|
||||
},
|
||||
@@ -470,7 +467,7 @@
|
||||
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
|
||||
</p>
|
||||
{/if}
|
||||
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
|
||||
{#each elements as { type, id, value, showPubkey, addSpaceBelow } (id)}
|
||||
{#if type === "new-messages"}
|
||||
<div
|
||||
{id}
|
||||
@@ -482,26 +479,8 @@
|
||||
</div>
|
||||
{:else if type === "date"}
|
||||
<Divider>{value}</Divider>
|
||||
{:else if shouldVirtualize}
|
||||
<VirtualItem root={element} initiallyVisible={i < 25}>
|
||||
{@const event = value as TrustedEvent}
|
||||
{#if event.kind === ROOM_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
<div class="cv">
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
{addSpaceBelow}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent} />
|
||||
</div>
|
||||
{/if}
|
||||
</VirtualItem>
|
||||
{:else}
|
||||
{@const event = value as TrustedEvent}
|
||||
{@const event = $state.snapshot(value as TrustedEvent)}
|
||||
{#if event.kind === ROOM_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
||||
import RoomItem from "@app/components/RoomItem.svelte"
|
||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||
import VirtualItem from "@lib/components/VirtualItem.svelte"
|
||||
|
||||
import RoomCompose from "@app/components/RoomCompose.svelte"
|
||||
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
||||
import RoomComposeParent from "@app/components/RoomComposeParent.svelte"
|
||||
import {userSettingsValues, decodeRelay, PROTECTED, MESSAGE_KINDS} from "@app/core/state"
|
||||
import {userSettingsValues, decodeRelay, PROTECTED} from "@app/core/state"
|
||||
import {prependParent, canEnforceNip70, publishDelete} from "@app/core/commands"
|
||||
import {checked} from "@app/util/notifications"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
@@ -38,7 +37,6 @@
|
||||
const url = decodeRelay($page.params.relay!)
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const at = $derived(parseInt($page.url.searchParams.get("at")!))
|
||||
const shouldVirtualize = $derived(isNaN(at))
|
||||
|
||||
const replyTo = (event: TrustedEvent) => {
|
||||
parent = event
|
||||
@@ -254,7 +252,7 @@
|
||||
url,
|
||||
at: at || now(),
|
||||
element: element!,
|
||||
filters: [{kinds: [...MESSAGE_KINDS, RELAY_ADD_MEMBER]}],
|
||||
filters: [{kinds: [MESSAGE, RELAY_ADD_MEMBER]}],
|
||||
onBackwardExhausted: () => {
|
||||
loadingBackward = false
|
||||
},
|
||||
@@ -307,7 +305,7 @@
|
||||
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
|
||||
</p>
|
||||
{/if}
|
||||
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
|
||||
{#each elements as { type, id, value, showPubkey, addSpaceBelow } (id)}
|
||||
{#if type === "new-messages"}
|
||||
<div
|
||||
{id}
|
||||
@@ -319,26 +317,8 @@
|
||||
</div>
|
||||
{:else if type === "date"}
|
||||
<Divider>{value}</Divider>
|
||||
{:else if shouldVirtualize}
|
||||
<VirtualItem root={element} initiallyVisible={i < 25}>
|
||||
{@const event = value as TrustedEvent}
|
||||
{#if event.kind === RELAY_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
<div>
|
||||
<RoomItem
|
||||
{url}
|
||||
{event}
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent}
|
||||
{addSpaceBelow} />
|
||||
</div>
|
||||
{/if}
|
||||
</VirtualItem>
|
||||
{:else}
|
||||
{@const event = value as TrustedEvent}
|
||||
{@const event = $state.snapshot(value as TrustedEvent)}
|
||||
{#if event.kind === RELAY_ADD_MEMBER}
|
||||
<RoomItemAddMember {url} {event} />
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user