forked from coracle/flotilla
Use address for page param for replaceable events
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {getTagValue} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
@@ -14,7 +15,6 @@
|
||||
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||
import {makeCalendarPath, makeSpacePath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -26,7 +26,7 @@
|
||||
const {url, event, showRoom, showActivity}: Props = $props()
|
||||
|
||||
const h = getTagValue("h", event.tags)
|
||||
const path = makeCalendarPath(url, event.id)
|
||||
const path = makeCalendarPath(url, getAddress(event))
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<Link
|
||||
class="col-3 card2 bg-alt w-full cursor-pointer shadow-md"
|
||||
href={makeCalendarPath(url, event.id)}>
|
||||
href={makeCalendarPath(url, getAddress(event))}>
|
||||
<CalendarEventHeader {event} />
|
||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
||||
<span class="whitespace-nowrap py-1 text-sm opacity-75">
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {getTagValue} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||
import EventActivity from "@app/components/EventActivity.svelte"
|
||||
import EventActions from "@app/components/EventActions.svelte"
|
||||
import ClassifiedEdit from "@app/components/ClassifiedEdit.svelte"
|
||||
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||
import {makeClassifiedPath, makeSpacePath} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
interface Props {
|
||||
url: string
|
||||
@@ -20,9 +26,11 @@
|
||||
const {url, event, showRoom, showActivity}: Props = $props()
|
||||
|
||||
const h = getTagValue("h", event.tags)
|
||||
const path = makeClassifiedPath(url, event.id)
|
||||
const path = makeClassifiedPath(url, getAddress(event))
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const editClassified = () => pushModal(ClassifiedEdit, {url, event})
|
||||
|
||||
const deleteReaction = async (event: TrustedEvent) =>
|
||||
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||
|
||||
@@ -41,5 +49,16 @@
|
||||
{#if showActivity}
|
||||
<EventActivity {url} {path} {event} />
|
||||
{/if}
|
||||
<EventActions {url} {event} noun="Listing" />
|
||||
<EventActions {url} {event} noun="Listing">
|
||||
{#snippet customActions()}
|
||||
{#if event.pubkey === $pubkey}
|
||||
<li>
|
||||
<Button onclick={editClassified}>
|
||||
<Icon size={4} icon={Pen2} />
|
||||
Edit Listing
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</EventActions>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {makeEvent, CLASSIFIED} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
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"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import ImagesInput from "@lib/components/ImagesInput.svelte"
|
||||
import CurrencyInput from "@app/components/CurrencyInput.svelte"
|
||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {canEnforceNip70, uploadFile} from "@app/core/commands"
|
||||
import ClassifiedForm from "@app/components/ClassifiedForm.svelte"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -27,148 +10,13 @@
|
||||
}
|
||||
|
||||
const {url, h}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const submit = async () => {
|
||||
loading = true
|
||||
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide a title for your listing.",
|
||||
})
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
|
||||
if (!content.trim()) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide a description for your listing.",
|
||||
})
|
||||
}
|
||||
|
||||
const tags = [...ed.storage.nostr.getEditorTags(), ["summary", content], ["title", title]]
|
||||
|
||||
try {
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
for (const image of images) {
|
||||
if (typeof image === "string") {
|
||||
tags.push(["image", image])
|
||||
} else {
|
||||
const {result, error} = await uploadFile(image, {url})
|
||||
|
||||
if (error) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: `Failed to upload file ${image.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
if (result) {
|
||||
tags.push(["image", result.url])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(CLASSIFIED, {content, tags}),
|
||||
})
|
||||
|
||||
history.back()
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const editor = makeEditor({
|
||||
url,
|
||||
submit,
|
||||
placeholder: "Provide a detailed description for your listing.",
|
||||
})
|
||||
|
||||
let title = $state("")
|
||||
let loading = $state(false)
|
||||
let currencyCode = $state("SAT")
|
||||
let currencyAmount = $state(0)
|
||||
let images = $state<(string | File)[]>([])
|
||||
</script>
|
||||
|
||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||
<ModalBody>
|
||||
<ClassifiedForm {url} {h}>
|
||||
{#snippet header()}
|
||||
<ModalHeader>
|
||||
<ModalTitle>Create a Classified Listing</ModalTitle>
|
||||
<ModalSubtitle>Advertise a job, sale, or need.</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
<div class="col-8 relative">
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Title*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
autofocus={!isMobile}
|
||||
bind:value={title}
|
||||
class="grow"
|
||||
type="text"
|
||||
placeholder="What is this listing for?" />
|
||||
</label>
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Description*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="note-editor flex-grow overflow-hidden">
|
||||
<EditorContent {editor} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Price*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="join grid grid-cols-2">
|
||||
<label class="join-item input input-bordered flex w-full items-center gap-2">
|
||||
<input bind:value={currencyAmount} class="grow w-32" type="number" />
|
||||
</label>
|
||||
<CurrencyInput class="join-item" bind:value={currencyCode} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Images (optional)</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<ImagesInput bind:value={images} />
|
||||
{/snippet}
|
||||
</Field>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Create Listing</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
{/snippet}
|
||||
</ClassifiedForm>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import {fromPairs} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {getTag, getTagValues} from "@welshman/util"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import ClassifiedForm from "@app/components/ClassifiedForm.svelte"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
event: TrustedEvent
|
||||
}
|
||||
|
||||
const {url, event}: Props = $props()
|
||||
|
||||
const {content} = event
|
||||
const {d, title, status} = fromPairs(event.tags)
|
||||
const [_, price = 0, currency = "SAT"] = getTag("price", event.tags) || []
|
||||
const images = getTagValues("image", event.tags)
|
||||
const initialValues = {d, title, status, content, price, currency, images}
|
||||
</script>
|
||||
|
||||
<ClassifiedForm {url} {initialValues}>
|
||||
{#snippet header()}
|
||||
<ModalHeader>
|
||||
<ModalTitle>Edit this Listing</ModalTitle>
|
||||
<ModalSubtitle>Advertise a job, sale, or need.</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
{/snippet}
|
||||
</ClassifiedForm>
|
||||
@@ -0,0 +1,204 @@
|
||||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import {randomId} from "@welshman/lib"
|
||||
import {makeEvent, CLASSIFIED} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
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"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import ImagesInput from "@lib/components/ImagesInput.svelte"
|
||||
import CurrencyInput from "@app/components/CurrencyInput.svelte"
|
||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {PROTECTED} from "@app/core/state"
|
||||
import {makeEditor} from "@app/editor"
|
||||
import {canEnforceNip70, uploadFile} from "@app/core/commands"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
h?: string
|
||||
header: Snippet
|
||||
initialValues?: {
|
||||
d?: string
|
||||
title?: string
|
||||
content?: string
|
||||
price?: number
|
||||
currency?: string
|
||||
images?: string[]
|
||||
status?: string
|
||||
}
|
||||
}
|
||||
|
||||
const {url, h, header, initialValues}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const submit = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
if (!title) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide a title for your listing.",
|
||||
})
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||
|
||||
if (!content.trim()) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide a description for your listing.",
|
||||
})
|
||||
}
|
||||
|
||||
const tags = [
|
||||
["d", initialValues?.d || randomId()],
|
||||
["title", title],
|
||||
["summary", content],
|
||||
["price", String(price), currency],
|
||||
["status", status],
|
||||
...ed.storage.nostr.getEditorTags(),
|
||||
]
|
||||
|
||||
if (await shouldProtect) {
|
||||
tags.push(PROTECTED)
|
||||
}
|
||||
|
||||
if (h) {
|
||||
tags.push(["h", h])
|
||||
}
|
||||
|
||||
for (const image of images) {
|
||||
if (typeof image === "string") {
|
||||
tags.push(["image", image])
|
||||
} else {
|
||||
const {result, error} = await uploadFile(image, {url})
|
||||
|
||||
if (error) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: `Failed to upload file ${image.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
if (result) {
|
||||
tags.push(["image", result.url])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: makeEvent(CLASSIFIED, {content, tags}),
|
||||
})
|
||||
|
||||
history.back()
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const content = initialValues?.content || ""
|
||||
const editor = makeEditor({url, submit, content})
|
||||
|
||||
let loading = $state(false)
|
||||
let title = $state(initialValues?.title || "")
|
||||
let status = $state(initialValues?.status || "active")
|
||||
let price = $state(Number(initialValues?.price || 0))
|
||||
let currency = $state(initialValues?.currency || "SAT")
|
||||
let images = $state<(string | File)[]>(initialValues?.images || [])
|
||||
</script>
|
||||
|
||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||
<ModalBody>
|
||||
<ModalHeader>
|
||||
<ModalTitle>Create a Classified Listing</ModalTitle>
|
||||
<ModalSubtitle>Advertise a job, sale, or need.</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
<div class="col-8 relative">
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Title*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
autofocus={!isMobile}
|
||||
bind:value={title}
|
||||
class="grow"
|
||||
type="text"
|
||||
placeholder="What is this listing for?" />
|
||||
</label>
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Description*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="note-editor flex-grow overflow-hidden">
|
||||
<EditorContent {editor} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Price*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="join grid grid-cols-2">
|
||||
<label class="join-item input input-bordered flex w-full items-center gap-2">
|
||||
<input bind:value={price} class="grow w-32" type="number" />
|
||||
</label>
|
||||
<CurrencyInput class="join-item" bind:value={currency} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Field>
|
||||
{#if initialValues}
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Status*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<select class="select select-bordered w-full" bind:value={status}>
|
||||
<option value="active">Active</option>
|
||||
<option value="sold">Sold</option>
|
||||
</select>
|
||||
{/snippet}
|
||||
</Field>
|
||||
{/if}
|
||||
<Field>
|
||||
{#snippet label()}
|
||||
<p>Images (optional)</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<ImagesInput bind:value={images} />
|
||||
{/snippet}
|
||||
</Field>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||
<Icon icon={AltArrowLeft} />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Create Listing</Spinner>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<Link
|
||||
class="col-2 card2 bg-alt w-full cursor-pointer shadow-xl"
|
||||
href={makeClassifiedPath(url, event.id)}>
|
||||
href={makeClassifiedPath(url, getAddress(event))}>
|
||||
{#if title}
|
||||
<div class="flex w-full items-center justify-between gap-2">
|
||||
<p class="text-xl">{title}</p>
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
const props: ComponentProps<typeof Content> = $props()
|
||||
</script>
|
||||
|
||||
{#if props.event.kind === EVENT_TIME}
|
||||
<NoteContentEventTime {...props} />
|
||||
{:else if props.event.kind === THREAD}
|
||||
<NoteContentThread {...props} />
|
||||
{:else if props.event.kind === CLASSIFIED}
|
||||
<NoteContentClassified {...props} />
|
||||
{:else if props.event.kind === ZAP_GOAL}
|
||||
<NoteContentGoal {...props} />
|
||||
{:else}
|
||||
<Content {...props} />
|
||||
{/if}
|
||||
{#key props.event.id}
|
||||
{#if props.event.kind === EVENT_TIME}
|
||||
<NoteContentEventTime {...props} />
|
||||
{:else if props.event.kind === THREAD}
|
||||
<NoteContentThread {...props} />
|
||||
{:else if props.event.kind === CLASSIFIED}
|
||||
<NoteContentClassified {...props} />
|
||||
{:else if props.event.kind === ZAP_GOAL}
|
||||
<NoteContentGoal {...props} />
|
||||
{:else}
|
||||
<Content {...props} />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{#if props.event.content}
|
||||
<Content {...props} />
|
||||
{/if}
|
||||
<div class="grid grid-cols-3 sm:grid-cols-5">
|
||||
<div class="grid grid-cols-3 sm:grid-cols-5 lg:grid-cols-9">
|
||||
{#each images as image (image)}
|
||||
<ContentLinkBlock event={props.event} value={{url: image}} />
|
||||
{/each}
|
||||
|
||||
@@ -531,7 +531,6 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
||||
|
||||
const ext = "." + type.split("/")[1]
|
||||
const server = await getBlossomServer(options)
|
||||
console.log("====", server)
|
||||
const hashes = [await sha256(await file.arrayBuffer())]
|
||||
const $signer = signer.get() || Nip01Signer.ephemeral()
|
||||
const authTemplate = makeBlossomAuthEvent({action: "upload", server, hashes})
|
||||
|
||||
@@ -232,13 +232,13 @@ export const notifications = derived(
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByClassifiedId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
const commentsByClassifiedAddress = groupBy(
|
||||
e => getTagValue("A", e.tags),
|
||||
classifiedComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [classifiedId, [comment]] of commentsByClassifiedId.entries()) {
|
||||
const classifiedItemPath = makeClassifiedPath(url, classifiedId)
|
||||
for (const [address, [comment]] of commentsByClassifiedAddress.entries()) {
|
||||
const classifiedItemPath = makeClassifiedPath(url, address)
|
||||
|
||||
if (hasNotification(classifiedPath, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
@@ -250,13 +250,13 @@ export const notifications = derived(
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByEventId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
const commentsByEventAddress = groupBy(
|
||||
e => getTagValue("A", e.tags),
|
||||
calendarComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [eventId, [comment]] of commentsByEventId.entries()) {
|
||||
const calendarItemPath = makeCalendarPath(url, eventId)
|
||||
for (const [address, [comment]] of commentsByEventAddress.entries()) {
|
||||
const calendarItemPath = makeCalendarPath(url, address)
|
||||
|
||||
if (hasNotification(calendarPath, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
|
||||
+20
-19
@@ -62,16 +62,13 @@ export const makeRoomPath = (url: string, h: string) => `/spaces/${encodeRelay(u
|
||||
|
||||
export const makeSpaceChatPath = (url: string) => makeRoomPath(url, "chat")
|
||||
|
||||
export const makeGoalPath = (url: string, eventId?: string) => makeSpacePath(url, "goals", eventId)
|
||||
export const makeGoalPath = (url: string, id?: string) => makeSpacePath(url, "goals", id)
|
||||
|
||||
export const makeThreadPath = (url: string, eventId?: string) =>
|
||||
makeSpacePath(url, "threads", eventId)
|
||||
export const makeThreadPath = (url: string, id?: string) => makeSpacePath(url, "threads", id)
|
||||
|
||||
export const makeClassifiedPath = (url: string, eventId?: string) =>
|
||||
makeSpacePath(url, "classifieds", eventId)
|
||||
export const makeClassifiedPath = (url: string, address?: string) => makeSpacePath(url, "classifieds", address)
|
||||
|
||||
export const makeCalendarPath = (url: string, eventId?: string) =>
|
||||
makeSpacePath(url, "calendar", eventId)
|
||||
export const makeCalendarPath = (url: string, address?: string) => makeSpacePath(url, "calendar", address)
|
||||
|
||||
export const getPrimaryNavItem = ($page: Page) => $page.route?.id?.split("/")[1]
|
||||
|
||||
@@ -126,17 +123,18 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
|
||||
}
|
||||
|
||||
if (event.kind === CLASSIFIED) {
|
||||
return makeClassifiedPath(url, event.id)
|
||||
return makeClassifiedPath(url, getAddress(event))
|
||||
}
|
||||
|
||||
if (event.kind === EVENT_TIME) {
|
||||
return makeCalendarPath(url, event.id)
|
||||
return makeCalendarPath(url, getAddress(event))
|
||||
}
|
||||
|
||||
if (event.kind === MESSAGE) {
|
||||
return h ? makeRoomPath(url, h) : makeSpacePath(url, "chat")
|
||||
}
|
||||
|
||||
const address = event.tags.find(nthEq(0, "A"))?.[1]
|
||||
const kind = event.tags.find(nthEq(0, "K"))?.[1]
|
||||
const id = event.tags.find(nthEq(0, "E"))?.[1]
|
||||
|
||||
@@ -149,18 +147,21 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
|
||||
return makeThreadPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === CLASSIFIED) {
|
||||
return makeClassifiedPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === EVENT_TIME) {
|
||||
return makeCalendarPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === MESSAGE) {
|
||||
return h ? makeRoomPath(url, h) : makeSpacePath(url, "chat")
|
||||
}
|
||||
}
|
||||
|
||||
if (address && kind) {
|
||||
if (parseInt(kind) === CLASSIFIED) {
|
||||
return makeClassifiedPath(url, address)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === EVENT_TIME) {
|
||||
return makeCalendarPath(url, address)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return entityLink(nip19.neventEncode({id: event.id, relays: urls}))
|
||||
@@ -171,10 +172,10 @@ export const getRoomItemPath = (url: string, event: TrustedEvent) => {
|
||||
case THREAD:
|
||||
return makeThreadPath(url, event.id)
|
||||
case CLASSIFIED:
|
||||
return makeClassifiedPath(url, event.id)
|
||||
return makeClassifiedPath(url, getAddress(event))
|
||||
case ZAP_GOAL:
|
||||
return makeGoalPath(url, event.id)
|
||||
case EVENT_TIME:
|
||||
return makeCalendarPath(url, event.id)
|
||||
return makeCalendarPath(url, getAddress(event))
|
||||
}
|
||||
}
|
||||
|
||||
+10
-8
@@ -24,10 +24,10 @@
|
||||
import {deriveEvent, decodeRelay} from "@app/core/state"
|
||||
import {setChecked} from "@app/util/notifications"
|
||||
|
||||
const {relay, id} = $page.params as MakeNonOptional<typeof $page.params>
|
||||
const {relay, address} = $page.params as MakeNonOptional<typeof $page.params>
|
||||
const url = decodeRelay(relay)
|
||||
const event = deriveEvent(id, [url])
|
||||
const filters = [{kinds: [COMMENT], "#E": [id]}]
|
||||
const event = deriveEvent(address, [url])
|
||||
const filters = [{kinds: [COMMENT], "#A": [address]}]
|
||||
const replies = deriveEventsAsc(deriveEventsById({filters, repository}))
|
||||
|
||||
const back = () => history.back()
|
||||
@@ -115,10 +115,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading listing...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load classified listing.</p>
|
||||
{/await}
|
||||
<div class="py-20 m-auto">
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading listing...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load classified listing.</p>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
@@ -140,7 +140,7 @@
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Link>
|
||||
{:else if event.kind === CLASSIFIED}
|
||||
<Link href={makeClassifiedPath(url, event.id)} class="btn btn-primary btn-sm">
|
||||
<Link href={makeClassifiedPath(url, getAddress(event))} class="btn btn-primary btn-sm">
|
||||
View Listing
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Link>
|
||||
@@ -150,7 +150,7 @@
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Link>
|
||||
{:else if event.kind === EVENT_TIME}
|
||||
<Link href={makeCalendarPath(url, event.id)} class="btn btn-primary btn-sm">
|
||||
<Link href={makeCalendarPath(url, getAddress(event))} class="btn btn-primary btn-sm">
|
||||
View Event
|
||||
<Icon icon={AltArrowRight} />
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user