Add image uploads to classifieds

This commit is contained in:
Jon Staab
2026-02-03 14:18:58 -08:00
parent 5427fd7860
commit dc5bac67aa
15 changed files with 324 additions and 179 deletions
+55 -38
View File
@@ -1,25 +1,25 @@
<script lang="ts">
import {writable} from "svelte/store"
import {makeEvent, CLASSIFIED} from "@welshman/util"
import {publishThunk} 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"
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} from "@app/core/commands"
import {canEnforceNip70, uploadFile} from "@app/core/commands"
type Props = {
url: string
@@ -30,14 +30,10 @@
const shouldProtect = canEnforceNip70(url)
const uploading = writable(false)
const back = () => history.back()
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
const submit = async () => {
if ($uploading) return
loading = true
if (!title) {
return pushToast({
@@ -52,33 +48,62 @@
if (!content.trim()) {
return pushToast({
theme: "error",
message: "Please provide a message for your listing.",
message: "Please provide a description for your listing.",
})
}
const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]]
const tags = [...ed.storage.nostr.getEditorTags(), ["summary", content], ["title", title]]
if (await shouldProtect) {
tags.push(PROTECTED)
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
}
if (h) {
tags.push(["h", h])
}
publishThunk({
relays: [url],
event: makeEvent(CLASSIFIED, {content, tags}),
})
history.back()
}
const editor = makeEditor({url, submit, uploading, placeholder: "What's on your mind?"})
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)}>
@@ -129,29 +154,21 @@
</Field>
<Field>
{#snippet label()}
<p>Images</p>
<p>Images (optional)</p>
{/snippet}
{#snippet input()}
todo: attach multiple images
<ImagesInput bind:value={images} />
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={Paperclip} size={3} />
{/if}
</Button>
</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 Listing</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Create Listing</Spinner>
</Button>
</ModalFooter>
</Modal>
+8 -1
View File
@@ -1,8 +1,9 @@
<script lang="ts">
import {formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {getTagValue} from "@welshman/util"
import {getTagValue, getTagValues} from "@welshman/util"
import Link from "@lib/components/Link.svelte"
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
import Content from "@app/components/Content.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
import ClassifiedActions from "@app/components/ClassifiedActions.svelte"
@@ -18,6 +19,7 @@
const title = getTagValue("title", event.tags)
const h = getTagValue("h", event.tags)
const images = getTagValues("image", event.tags)
</script>
<Link
@@ -36,6 +38,11 @@
</p>
{/if}
<Content {event} {url} expandMode="inline" />
<div class="grid grid-cols-3 sm:grid-cols-5">
{#each images as image (image)}
<ContentLinkBlock {event} value={{url: image}} />
{/each}
</div>
<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">
Posted by
+6 -1
View File
@@ -186,7 +186,12 @@
{/if}
{:else if isEllipsis(parsed) && expandInline}
{@html renderAsHtml(parsed)}
<button type="button" class="text-sm underline"> Read more </button>
<button
type="button"
class="text-sm underline"
onclick={stopPropagation(preventDefault(expand))}>
Read more
</button>
{:else}
{@html renderAsHtml(parsed)}
{/if}
+1 -1
View File
@@ -3,7 +3,7 @@
import {writable} from "svelte/store"
import type {Writable} from "svelte/store"
import type {Instance} from "tippy.js"
import {preventDefault} from '@lib/html'
import {preventDefault} from "@lib/html"
import {createSearch} from "@welshman/app"
import {currencyOptions, displayCurrency} from "@lib/currency"
import Suggestions from "@lib/components/Suggestions.svelte"
+4 -9
View File
@@ -6,7 +6,6 @@
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {publishComment, canEnforceNip70} from "@app/core/commands"
import {PROTECTED} from "@app/core/state"
@@ -65,12 +64,8 @@
</script>
<div bind:this={spacer}></div>
<form
in:fly
bind:this={form}
onsubmit={preventDefault(submit)}
class="cb cw fixed z-feature -mx-2 pt-3">
<div class="card2 mx-2 my-2 bg-neutral">
<form in:fly bind:this={form} onsubmit={preventDefault(submit)} class="cb cw fixed z-feature pt-3">
<div class="card2 mx-2 my-2 bg-alt shadow-md">
<div class="relative">
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
@@ -86,9 +81,9 @@
{/if}
</Button>
</div>
<ModalFooter>
<div class="flex justify-between pt-3">
<Button class="btn btn-link" onclick={onClose}>Cancel</Button>
<Button type="submit" class="btn btn-primary">Post Reply</Button>
</ModalFooter>
</div>
</div>
</form>
+100 -96
View File
@@ -10,6 +10,8 @@
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import LogOut from "@app/components/LogOut.svelte"
import {PLATFORM_NAME} from "@app/core/state"
@@ -21,99 +23,101 @@
const toggleTheme = () => theme.set($theme === "dark" ? "light" : "dark")
</script>
<div class="column menu gap-2">
<Link replaceState href="/settings/profile">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={UserRounded} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Profile</div>
{/snippet}
{#snippet info()}
<div>Customize your user profile</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/alerts">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Bell} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Alerts</div>
{/snippet}
{#snippet info()}
<div>Set up email digests and push notifications</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/wallet">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Wallet} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Wallet</div>
{/snippet}
{#snippet info()}
<div>Connect a bitcoin wallet for sending social tips</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/relays">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Server} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Relays</div>
{/snippet}
{#snippet info()}
<div>Control how {PLATFORM_NAME} talks to the network</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/content">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Settings} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Settings</div>
{/snippet}
{#snippet info()}
<div>Get into the details about how {PLATFORM_NAME} works</div>
{/snippet}
</CardButton>
</Link>
<Button onclick={toggleTheme}>
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Moon} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Theme</div>
{/snippet}
{#snippet info()}
<div>Switch between light and dark mode</div>
{/snippet}
</CardButton>
</Button>
<Link replaceState href="/settings/about">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Code2} size={7} /></div>
{/snippet}
{#snippet title()}
<div>About</div>
{/snippet}
{#snippet info()}
<div>Learn about {PLATFORM_NAME} and support the developer</div>
{/snippet}
</CardButton>
</Link>
<Button onclick={logout} class="btn btn-neutral">
<Icon icon={Exit} /> Log Out
</Button>
</div>
<Modal>
<ModalBody>
<Link replaceState href="/settings/profile">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={UserRounded} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Profile</div>
{/snippet}
{#snippet info()}
<div>Customize your user profile</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/alerts">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Bell} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Alerts</div>
{/snippet}
{#snippet info()}
<div>Set up email digests and push notifications</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/wallet">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Wallet} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Wallet</div>
{/snippet}
{#snippet info()}
<div>Connect a bitcoin wallet for sending social tips</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/relays">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Server} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Relays</div>
{/snippet}
{#snippet info()}
<div>Control how {PLATFORM_NAME} talks to the network</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/content">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Settings} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Settings</div>
{/snippet}
{#snippet info()}
<div>Get into the details about how {PLATFORM_NAME} works</div>
{/snippet}
</CardButton>
</Link>
<Button onclick={toggleTheme}>
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Moon} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Theme</div>
{/snippet}
{#snippet info()}
<div>Switch between light and dark mode</div>
{/snippet}
</CardButton>
</Button>
<Link replaceState href="/settings/about">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Code2} size={7} /></div>
{/snippet}
{#snippet title()}
<div>About</div>
{/snippet}
{#snippet info()}
<div>Learn about {PLATFORM_NAME} and support the developer</div>
{/snippet}
</CardButton>
</Link>
<Button onclick={logout} class="btn btn-neutral">
<Icon icon={Exit} /> Log Out
</Button>
</ModalBody>
</Modal>
@@ -1,11 +1,13 @@
<script lang="ts">
import type {ComponentProps} from "svelte"
import {getTagValue} from "@welshman/util"
import {getTagValue, getTagValues} from "@welshman/util"
import Content from "@app/components/Content.svelte"
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
const props: ComponentProps<typeof Content> = $props()
const title = getTagValue("title", props.event.tags)
const images = getTagValues("image", props.event.tags)
</script>
<div class="flex flex-col gap-2">
@@ -15,4 +17,9 @@
{#if props.event.content}
<Content {...props} />
{/if}
<div class="grid grid-cols-3 sm:grid-cols-5">
{#each images as image (image)}
<ContentLinkBlock event={props.event} value={{url: image}} />
{/each}
</div>
</div>
+3 -17
View File
@@ -43,6 +43,7 @@ import {
toNostrURI,
RelayMode,
getTagValues,
uploadBlob,
canUploadBlob,
encryptFile,
makeBlossomAuthEvent,
@@ -50,7 +51,6 @@ import {
editProfile,
createProfile,
uniqTags,
makeHttpAuthHeader,
} from "@welshman/util"
import {Pool, AuthStatus, SocketStatus} from "@welshman/net"
import {Router} from "@welshman/router"
@@ -509,8 +509,6 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
try {
const {name, type} = file
console.log("======== 1", name, type, options.encrypt)
if (!type.match("image/(webp|gif|svg)")) {
file = await compressFile(file, options)
}
@@ -532,25 +530,14 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
}
const ext = "." + type.split("/")[1]
console.log("======== 2", ext)
const server = await getBlossomServer(options)
console.log("======== 3", server)
console.log("====", server)
const hashes = [await sha256(await file.arrayBuffer())]
const $signer = signer.get() || Nip01Signer.ephemeral()
const authTemplate = makeBlossomAuthEvent({action: "upload", server, hashes})
const authEvent = await $signer.sign(authTemplate)
console.log("======== 4", authEvent.id)
// const res = await uploadBlob(server, file, {authEvent})
const res = await fetch(`${new URL(server).origin}/upload`, {
method: "PUT",
headers: {Authorization: makeHttpAuthHeader(authEvent)},
body: file instanceof Blob ? file : new Blob([file]),
})
console.log("======== 5", res.status)
const res = await uploadBlob(server, file, {authEvent})
const text = await res.text()
console.log("======== 6", text)
let {uploaded, url, ...task} = parseJson(text) || {}
@@ -569,7 +556,6 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
return {result}
} catch (e: any) {
console.log("========= error", String(e))
console.error("Error caught when uploading file:", e)
return {error: e.toString()}
+125
View File
@@ -0,0 +1,125 @@
<script lang="ts">
import {randomId} from "@welshman/lib"
import {removeAt, insertAt} from "@welshman/lib"
import {preventDefault, stopPropagation} from "@lib/html"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
interface Props {
value: (string | File)[]
multiple?: boolean
}
let {value = $bindable(), multiple = true}: Props = $props()
const id = randomId()
const getImageUrl = (item: string | File): string => {
if (typeof item === "string") return item
return URL.createObjectURL(item)
}
const addFiles = (files: FileList | File[]) => {
const newFiles = Array.from(files).filter(file => file.type.startsWith("image/"))
value = [...value, ...newFiles]
}
const removeItem = (index: number) => {
value = removeAt(index, value)
}
const onFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
if (target.files?.length) {
addFiles(target.files)
target.value = ""
}
}
const onDrop = (e: Event) => {
dropActive = false
const dragEvent = e as DragEvent
if (dragEvent.dataTransfer?.files?.length) {
addFiles(dragEvent.dataTransfer.files)
}
}
const onDragEnter = (e: Event) => {
dropActive = true
}
const onDragOver = (e: Event) => {
dropActive = true
}
const onDragLeave = (e: Event) => {
dropActive = false
}
let draggedIndex: number | null = $state(null)
let dropActive = $state(false)
const handleDragStart = (e: DragEvent, index: number) => {
draggedIndex = index
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = "move"
}
}
const handleDragOver = (e: DragEvent, index: number) => {
e.preventDefault()
if (draggedIndex !== null && draggedIndex !== index) {
value = insertAt(index, value[draggedIndex], removeAt(draggedIndex, value))
draggedIndex = index
}
}
const handleDragEnd = () => {
draggedIndex = null
}
</script>
<div class="flex flex-col gap-2">
<div class="grid grid-cols-3 gap-3" role="list">
{#each value as item, index (index)}
<div
class="relative aspect-square cursor-move"
class:border-primary={draggedIndex === index}
draggable="true"
role="listitem"
aria-label="Draggable image"
ondragstart={e => handleDragStart(e, index)}
ondragover={e => handleDragOver(e, index)}
ondragend={handleDragEnd}>
<img
src={getImageUrl(item)}
alt="Upload preview"
class="h-full w-full object-cover rounded-box" />
<Button
class="absolute right-1 top-1 w-5 h-5 flex justify-center items-center rounded-full bg-base-100"
onclick={() => removeItem(index)}>
<Icon icon={CloseCircle} size={6} />
</Button>
</div>
{/each}
<label
for={id}
class="flex cursor-pointer aspect-square items-center justify-center rounded-lg border-2 border-dashed border-base-content text-sm"
class:border-primary={dropActive}
aria-label="Drag and drop images here or click to select"
ondragenter={stopPropagation(preventDefault(onDragEnter))}
ondragover={stopPropagation(preventDefault(onDragOver))}
ondragleave={stopPropagation(preventDefault(onDragLeave))}
ondrop={stopPropagation(preventDefault(onDrop))}>
<div class="flex flex-col items-center gap-2 text-center">
<Icon icon={GallerySend} size={8} />
<p class="text-sm opacity-70">Drag and drop images or click to select</p>
</div>
</label>
</div>
<input {id} type="file" accept="image/*" {multiple} onchange={onFileChange} class="hidden" />
</div>
+1 -1
View File
@@ -12,6 +12,6 @@
const {children, tag = "div", ...props}: Props = $props()
</script>
<svelte:element this={tag} {...props} class={cx("flex flex-col overflow-hidden pb-6", props.class)}>
<svelte:element this={tag} {...props} class={cx("flex flex-col overflow-hidden", props.class)}>
{@render children?.()}
</svelte:element>
+1 -2
View File
@@ -10,7 +10,6 @@
const {children, ...props}: Props = $props()
</script>
<div
class={cx("scroll-container overflow-y-auto min-h-0 flex flex-col gap-4 p-6 pb-0", props.class)}>
<div class={cx("scroll-container overflow-y-auto min-h-0 flex flex-col gap-4 p-6", props.class)}>
{@render children?.()}
</div>
@@ -15,7 +15,7 @@
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import SpaceMenuButton from "@app/components/SpaceMenuButton.svelte"
import CalendarEventActions from "@app/components/CalendarEventActions.svelte"
@@ -89,7 +89,7 @@
<div class="flex py-2 opacity-50">
<div class="h-px flex-grow bg-base-content opacity-25"></div>
</div>
<Content showEntire event={$event} {url} />
<NoteContent showEntire event={$event} {url} />
</div>
</div>
<div class="flex w-full flex-col justify-end sm:flex-row">
@@ -107,7 +107,7 @@
{#each sortBy(e => e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={reply} {url} />
<NoteContent showEntire event={reply} {url} />
<CalendarEventActions event={reply} {url} />
</div>
</NoteCard>
@@ -15,7 +15,7 @@
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import SpaceMenuButton from "@app/components/SpaceMenuButton.svelte"
import ClassifiedActions from "@app/components/ClassifiedActions.svelte"
@@ -83,7 +83,7 @@
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={$event} {url} />
<NoteContent showEntire event={$event} {url} />
<ClassifiedActions showRoom event={$event} {url} />
</div>
</NoteCard>
@@ -98,7 +98,7 @@
{#each $replies.slice(0, showAll ? undefined : 4) as reply (reply.id)}
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={reply} {url} />
<NoteContent showEntire event={reply} {url} />
<CommentActions segment="classifieds" event={reply} {url} />
</div>
</NoteCard>
@@ -15,7 +15,7 @@
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import SpaceMenuButton from "@app/components/SpaceMenuButton.svelte"
import GoalSummary from "@app/components/GoalSummary.svelte"
@@ -85,7 +85,7 @@
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={{...$event, content: summary}} {url} />
<NoteContent showEntire event={{...$event, content: summary}} {url} />
<GoalSummary event={$event} {url} />
<GoalActions showRoom event={$event} {url} />
</div>
@@ -101,7 +101,7 @@
{#each $replies.slice(0, showAll ? undefined : 4) as reply (reply.id)}
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={reply} {url} />
<NoteContent showEntire event={reply} {url} />
<CommentActions segment="goals" event={reply} {url} />
</div>
</NoteCard>
@@ -15,7 +15,7 @@
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import SpaceMenuButton from "@app/components/SpaceMenuButton.svelte"
import ThreadActions from "@app/components/ThreadActions.svelte"
@@ -83,7 +83,7 @@
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={$event} {url} />
<NoteContent showEntire event={$event} {url} />
<ThreadActions showRoom event={$event} {url} />
</div>
</NoteCard>
@@ -98,7 +98,7 @@
{#each $replies.slice(0, showAll ? undefined : 4) as reply (reply.id)}
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={reply} {url} />
<NoteContent showEntire event={reply} {url} />
<CommentActions segment="threads" event={reply} {url} />
</div>
</NoteCard>