Show which item raised an unread dot on every content board (#419)
Co-authored-by: Coracle-Bot <npub1klq6260@nostr.local>
This commit is contained in:
parent
4d7b052724
commit
bec000b05c
25 changed files with 142 additions and 89 deletions
|
|
@ -1577,6 +1577,18 @@ Acceptance:
|
||||||
- The thread list below the md breakpoint is a list of links rather than a
|
- The thread list below the md breakpoint is a list of links rather than a
|
||||||
table, and alice's thread carries a dot there; bob's own does not.
|
table, and alice's thread carries a dot there; bob's own does not.
|
||||||
|
|
||||||
|
### US-114 — See which listings are unread
|
||||||
|
|
||||||
|
As bob, I want the same dot on a board whose items are cards rather than rows,
|
||||||
|
so that every content section answers "which one is new" the same way.
|
||||||
|
|
||||||
|
Acceptance:
|
||||||
|
|
||||||
|
- A listing alice posted raises an unread dot on the Classifieds nav item; one
|
||||||
|
bob posted himself does not.
|
||||||
|
- Opening the list keeps the dot on the corner of alice's card, and bob's own
|
||||||
|
card carries none.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
Features the e2e suite cannot exercise, and what stops it.
|
Features the e2e suite cannot exercise, and what stops it.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {neventEncode, npubEncode} from "nostr-tools/nip19"
|
import {neventEncode, npubEncode} from "nostr-tools/nip19"
|
||||||
import {HOUR, MINUTE} from "@welshman/lib"
|
import {HOUR, MINUTE} from "@welshman/lib"
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import {MessagingRelayList, RelayList, Thread} from "@welshman/domain"
|
import {Classified, MessagingRelayList, RelayList, Thread} from "@welshman/domain"
|
||||||
import type {Locator, Page} from "@playwright/test"
|
import type {Locator, Page} from "@playwright/test"
|
||||||
import {expect, roomPath, spacePath, test, users} from "../harness"
|
import {expect, roomPath, spacePath, test, users} from "../harness"
|
||||||
import type {SeededSpace, TestUser} from "../harness"
|
import type {SeededSpace, TestUser} from "../harness"
|
||||||
|
|
@ -481,6 +481,22 @@ test("US-110 see another space's unread activity from a phone", async ({seed, as
|
||||||
const contentNavItem = (page: Page, name: string) =>
|
const contentNavItem = (page: Page, name: string) =>
|
||||||
page.locator(".secondary-nav").getByRole("link", {name})
|
page.locator(".secondary-nav").getByRole("link", {name})
|
||||||
|
|
||||||
|
const seedClassified = (space: SeededSpace, user: TestUser, title: string, createdAt: number) =>
|
||||||
|
space.event(
|
||||||
|
user,
|
||||||
|
() =>
|
||||||
|
space
|
||||||
|
.kind(Classified)
|
||||||
|
.writer()
|
||||||
|
.setRoom(space.url, "general")
|
||||||
|
.setIdentifier()
|
||||||
|
.setTitle(title)
|
||||||
|
.setPrice(100)
|
||||||
|
.setContent("in good condition")
|
||||||
|
.renderTemplate(),
|
||||||
|
createdAt,
|
||||||
|
)
|
||||||
|
|
||||||
const seedThread = (space: SeededSpace, user: TestUser, title: string, createdAt: number) =>
|
const seedThread = (space: SeededSpace, user: TestUser, title: string, createdAt: number) =>
|
||||||
space.event(
|
space.event(
|
||||||
user,
|
user,
|
||||||
|
|
@ -579,3 +595,39 @@ test("US-113 see which threads are unread on a phone", async ({seed, as}) => {
|
||||||
await expect(unreadDot(hers)).toBeVisible()
|
await expect(unreadDot(hers)).toBeVisible()
|
||||||
await expect(unreadDot(his)).toHaveCount(0)
|
await expect(unreadDot(his)).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Classifieds stands in for the five boards whose items are cards rather than rows — they all
|
||||||
|
// render the same UnreadDot off the same content path, and only the corner it sits in differs.
|
||||||
|
test("US-114 see which listings are unread", async ({seed, as}) => {
|
||||||
|
const scenario = await seed(({relay, user, at}) => {
|
||||||
|
const space = relay("space")
|
||||||
|
|
||||||
|
space.room("general", {name: "General"})
|
||||||
|
space.join(user.alice, "general")
|
||||||
|
space.join(user.bob, "general")
|
||||||
|
|
||||||
|
seedClassified(space, user.bob, "vintage sextant", at(3, HOUR))
|
||||||
|
seedClassified(space, user.alice, "brass astrolabe", at(2, HOUR))
|
||||||
|
})
|
||||||
|
|
||||||
|
const space = scenario.space("space")
|
||||||
|
const bob = await as(users.bob, roomPath(space.url, "general"))
|
||||||
|
|
||||||
|
const classifiedsNav = contentNavItem(bob, "Classifieds")
|
||||||
|
|
||||||
|
await expect(classifiedsNav).toBeVisible()
|
||||||
|
await expect(unreadDot(classifiedsNav)).toBeVisible()
|
||||||
|
|
||||||
|
await classifiedsNav.click()
|
||||||
|
|
||||||
|
const hers = bob.getByRole("link").filter({hasText: "brass astrolabe"})
|
||||||
|
const his = bob.getByRole("link").filter({hasText: "vintage sextant"})
|
||||||
|
|
||||||
|
await expect(his).toBeVisible()
|
||||||
|
|
||||||
|
// Same tick and throttle as US-112.
|
||||||
|
await bob.waitForTimeout(1500)
|
||||||
|
|
||||||
|
await expect(unreadDot(hers)).toBeVisible()
|
||||||
|
await expect(unreadDot(his)).toHaveCount(0)
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {uniq} from "@welshman/lib"
|
import {uniq} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {getAddress} from "@welshman/util"
|
|
||||||
import {Article} from "@welshman/domain"
|
import {Article} from "@welshman/domain"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import RoomName from "@app/components/RoomName.svelte"
|
import RoomName from "@app/components/RoomName.svelte"
|
||||||
|
|
@ -14,7 +13,7 @@
|
||||||
import EventActionBar from "@app/components/EventActionBar.svelte"
|
import EventActionBar from "@app/components/EventActionBar.svelte"
|
||||||
import {reader} from "@app/core"
|
import {reader} from "@app/core"
|
||||||
import {deriveIsDeleted} from "@app/repository"
|
import {deriveIsDeleted} from "@app/repository"
|
||||||
import {makeArticlePath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -31,7 +30,6 @@
|
||||||
const article = $derived(reader(Article)(event))
|
const article = $derived(reader(Article)(event))
|
||||||
const h = $derived(article.room())
|
const h = $derived(article.room())
|
||||||
const topics = $derived(article.topics())
|
const topics = $derived(article.topics())
|
||||||
const path = $derived(makeArticlePath(url, getAddress(event)))
|
|
||||||
const deleted = $derived(deriveIsDeleted(event))
|
const deleted = $derived(deriveIsDeleted(event))
|
||||||
|
|
||||||
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
||||||
|
|
@ -54,7 +52,7 @@
|
||||||
<EventActionBar {url} {event} noun="Article">
|
<EventActionBar {url} {event} noun="Article">
|
||||||
{#snippet leading()}
|
{#snippet leading()}
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} size="sm" hideLastActive />
|
<EventActivity {event} {context} size="sm" hideLastActive />
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</EventActionBar>
|
</EventActionBar>
|
||||||
|
|
@ -84,7 +82,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
</ThunkStatusOrDeleted>
|
</ThunkStatusOrDeleted>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import ArticleActions from "@app/components/ArticleActions.svelte"
|
import ArticleActions from "@app/components/ArticleActions.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import {reader} from "@app/core"
|
import {reader} from "@app/core"
|
||||||
import {makeArticlePath} from "@app/routes"
|
import {makeArticlePath} from "@app/routes"
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
|
|
||||||
const {url, event, context}: Props = $props()
|
const {url, event, context}: Props = $props()
|
||||||
|
|
||||||
|
const path = $derived(makeArticlePath(url, getAddress(event)))
|
||||||
const article = $derived(reader(Article)(event))
|
const article = $derived(reader(Article)(event))
|
||||||
const title = $derived(article.title())
|
const title = $derived(article.title())
|
||||||
const summary = $derived(article.summary())
|
const summary = $derived(article.summary())
|
||||||
|
|
@ -28,10 +30,8 @@
|
||||||
<div data-component="ArticleItem" class="cv relative w-full card card-interactive">
|
<div data-component="ArticleItem" class="cv relative w-full card card-interactive">
|
||||||
<!-- An overlay rather than a wrapper: the card carries a profile button and the room and action
|
<!-- An overlay rather than a wrapper: the card carries a profile button and the room and action
|
||||||
links, and none of those may sit inside an anchor. -->
|
links, and none of those may sit inside an anchor. -->
|
||||||
<Link
|
<Link class="absolute inset-0 rounded-2xl" href={path} aria-label={title || "Untitled"} />
|
||||||
class="absolute inset-0 rounded-2xl"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makeArticlePath(url, getAddress(event))}
|
|
||||||
aria-label={title || "Untitled"} />
|
|
||||||
<div class="pointer-events-none relative flex flex-col gap-2">
|
<div class="pointer-events-none relative flex flex-col gap-2">
|
||||||
{#if image}
|
{#if image}
|
||||||
<img src={image} alt="" class="h-40 w-full rounded-2xl object-cover" />
|
<img src={image} alt="" class="h-40 w-full rounded-2xl object-cover" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {getAddress, tagSpec, tagValue} from "@welshman/util"
|
import {tagSpec, tagValue} from "@welshman/util"
|
||||||
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
|
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
|
||||||
import {user} from "@app/core"
|
import {user} from "@app/core"
|
||||||
import {makeCalendarPath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
const {url, event, showRoom, showActivity, context}: Props = $props()
|
const {url, event, showRoom, showActivity, context}: Props = $props()
|
||||||
|
|
||||||
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
||||||
const path = $derived(makeCalendarPath(url, getAddress(event)))
|
|
||||||
|
|
||||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||||
|
|
||||||
|
|
@ -52,7 +51,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip tip-left" />
|
reactionClass="tip tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Event">
|
<EventActions {url} {event} noun="Event">
|
||||||
{#snippet customActions()}
|
{#snippet customActions()}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import RoomLink from "@app/components/RoomLink.svelte"
|
import RoomLink from "@app/components/RoomLink.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import {makeCalendarPath} from "@app/routes"
|
import {makeCalendarPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -17,12 +18,12 @@
|
||||||
|
|
||||||
const {url, event, context}: Props = $props()
|
const {url, event, context}: Props = $props()
|
||||||
|
|
||||||
|
const path = $derived(makeCalendarPath(url, getAddress(event)))
|
||||||
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link class="cv relative flex flex-col gap-3 card card-interactive w-full" href={path}>
|
||||||
class="cv flex flex-col gap-3 card card-interactive w-full"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makeCalendarPath(url, getAddress(event))}>
|
|
||||||
<CalendarEventHeader {event} />
|
<CalendarEventHeader {event} />
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<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">
|
<span class="whitespace-nowrap py-1 text-sm opacity-75">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {uniq} from "@welshman/lib"
|
import {uniq} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {getAddress} from "@welshman/util"
|
|
||||||
import {Classified} from "@welshman/domain"
|
import {Classified} from "@welshman/domain"
|
||||||
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||||
import {normalizeTopic} from "@lib/util"
|
import {normalizeTopic} from "@lib/util"
|
||||||
|
|
@ -18,7 +17,7 @@
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import ClassifiedEdit from "@app/components/ClassifiedEdit.svelte"
|
import ClassifiedEdit from "@app/components/ClassifiedEdit.svelte"
|
||||||
import {reader, user} from "@app/core"
|
import {reader, user} from "@app/core"
|
||||||
import {makeClassifiedPath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -36,7 +35,6 @@
|
||||||
const classified = $derived(reader(Classified)(event))
|
const classified = $derived(reader(Classified)(event))
|
||||||
const h = $derived(classified.room())
|
const h = $derived(classified.room())
|
||||||
const topics = $derived(classified.topics() ?? [])
|
const topics = $derived(classified.topics() ?? [])
|
||||||
const path = $derived(makeClassifiedPath(url, getAddress(event)))
|
|
||||||
|
|
||||||
const editClassified = () => pushModal(ClassifiedEdit, {url, event})
|
const editClassified = () => pushModal(ClassifiedEdit, {url, event})
|
||||||
|
|
||||||
|
|
@ -70,7 +68,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Listing">
|
<EventActions {url} {event} noun="Listing">
|
||||||
{#snippet customActions()}
|
{#snippet customActions()}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import ClassifiedActions from "@app/components/ClassifiedActions.svelte"
|
import ClassifiedActions from "@app/components/ClassifiedActions.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import RoomLink from "@app/components/RoomLink.svelte"
|
import RoomLink from "@app/components/RoomLink.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import {makeClassifiedPath} from "@app/routes"
|
import {makeClassifiedPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
|
|
||||||
const {url, event, context}: Props = $props()
|
const {url, event, context}: Props = $props()
|
||||||
|
|
||||||
|
const path = $derived(makeClassifiedPath(url, getAddress(event)))
|
||||||
const classified = $derived(reader(Classified)(event))
|
const classified = $derived(reader(Classified)(event))
|
||||||
const title = $derived(classified.title())
|
const title = $derived(classified.title())
|
||||||
const h = $derived(classified.room())
|
const h = $derived(classified.room())
|
||||||
|
|
@ -29,9 +31,8 @@
|
||||||
const price = $derived(classified.price())
|
const price = $derived(classified.price())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
class="cv flex flex-col gap-2 card card-interactive w-full"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makeClassifiedPath(url, getAddress(event))}>
|
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="flex w-full items-center justify-between gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
<p class="text-xl">
|
<p class="text-xl">
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,18 @@
|
||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import {makeSpacePath} from "@app/routes"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
segment?: string
|
|
||||||
showActivity?: boolean
|
showActivity?: boolean
|
||||||
context: FeedContext
|
context: FeedContext
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, event, segment, showActivity = false, context}: Props = $props()
|
const {url, event, showActivity = false, context}: Props = $props()
|
||||||
|
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
|
|
||||||
const path = segment && makeSpacePath(url, segment, event.id)
|
|
||||||
|
|
||||||
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
||||||
|
|
||||||
const createReaction = (values: EventContent) => publishReaction(event, values, {url, h})
|
const createReaction = (values: EventContent) => publishReaction(event, values, {url, h})
|
||||||
|
|
@ -38,8 +34,8 @@
|
||||||
{deleteReaction}
|
{deleteReaction}
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity && path}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Comment" />
|
<EventActions {url} {event} noun="Comment" />
|
||||||
</ThunkStatusOrDeleted>
|
</ThunkStatusOrDeleted>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {filter, max, gt, spec, formatTimestampRelative} from "@welshman/lib"
|
import {filter, max, spec, formatTimestampRelative} from "@welshman/lib"
|
||||||
import {COMMENT} from "@welshman/util"
|
import {COMMENT} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import {deriveChecked} from "@app/notifications"
|
|
||||||
|
|
||||||
// hideLastActive: redundant next to a detail page's own comments below.
|
// hideLastActive: redundant next to a detail page's own comments below.
|
||||||
const {
|
const {
|
||||||
path,
|
|
||||||
event,
|
event,
|
||||||
context,
|
context,
|
||||||
size = "xs",
|
size = "xs",
|
||||||
hideLastActive,
|
hideLastActive,
|
||||||
}: {
|
}: {
|
||||||
path: string
|
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
context: FeedContext
|
context: FeedContext
|
||||||
size?: "xs" | "sm"
|
size?: "xs" | "sm"
|
||||||
|
|
@ -25,7 +22,6 @@
|
||||||
|
|
||||||
const buttonClass = `button button-neutral button-${size} rounded-full`
|
const buttonClass = `button button-neutral button-${size} rounded-full`
|
||||||
|
|
||||||
const checked = deriveChecked(path)
|
|
||||||
const related = context.related(event)
|
const related = context.related(event)
|
||||||
const replies = derived(related, $related => filter(spec({kind: COMMENT}), $related))
|
const replies = derived(related, $related => filter(spec({kind: COMMENT}), $related))
|
||||||
const lastActive = $derived(max([...$replies, event].map(e => e.created_at)))
|
const lastActive = $derived(max([...$replies, event].map(e => e.created_at)))
|
||||||
|
|
@ -36,10 +32,7 @@
|
||||||
<span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span>
|
<span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span>
|
||||||
</div>
|
</div>
|
||||||
{#if !hideLastActive}
|
{#if !hideLastActive}
|
||||||
<div class="{buttonClass} relative">
|
<div class={buttonClass}>
|
||||||
{#if gt(lastActive, $checked)}
|
|
||||||
<div class="h-2 w-2 rounded-full bg-primary text-primary-content"></div>
|
|
||||||
{/if}
|
|
||||||
Active {formatTimestampRelative(lastActive)}
|
Active {formatTimestampRelative(lastActive)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import RoomName from "@app/components/RoomName.svelte"
|
import RoomName from "@app/components/RoomName.svelte"
|
||||||
import {makeGoalPath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
const {url, event, showRoom, showActivity, context}: Props = $props()
|
const {url, event, showRoom, showActivity, context}: Props = $props()
|
||||||
|
|
||||||
const path = makeGoalPath(url, event.id)
|
|
||||||
const goal = reader(ZapGoal)(event)
|
const goal = reader(ZapGoal)(event)
|
||||||
|
|
||||||
const h = goal.room()
|
const h = goal.room()
|
||||||
|
|
@ -47,7 +46,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} hideZap noun="Goal" />
|
<EventActions {url} {event} hideZap noun="Goal" />
|
||||||
</ThunkStatusOrDeleted>
|
</ThunkStatusOrDeleted>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import GoalSummary from "@app/components/GoalSummary.svelte"
|
import GoalSummary from "@app/components/GoalSummary.svelte"
|
||||||
import RoomLink from "@app/components/RoomLink.svelte"
|
import RoomLink from "@app/components/RoomLink.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import {makeGoalPath} from "@app/routes"
|
import {makeGoalPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -21,14 +22,14 @@
|
||||||
|
|
||||||
const goal = reader(ZapGoal)(event)
|
const goal = reader(ZapGoal)(event)
|
||||||
|
|
||||||
|
const path = makeGoalPath(url, event.id)
|
||||||
const title = goal.title()
|
const title = goal.title()
|
||||||
const summary = goal.summary()
|
const summary = goal.summary()
|
||||||
const h = goal.room()
|
const h = goal.room()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
class="cv flex flex-col gap-2 card card-interactive w-full"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makeGoalPath(url, event.id)}>
|
|
||||||
<p class="text-2xl">{title}</p>
|
<p class="text-2xl">{title}</p>
|
||||||
<Content
|
<Content
|
||||||
event={{content: summary, tags: event.tags}}
|
event={{content: summary, tags: event.tags}}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import MenuButton from "@lib/components/MenuButton.svelte"
|
import MenuButton from "@lib/components/MenuButton.svelte"
|
||||||
import BoardMenu from "@app/components/BoardMenu.svelte"
|
import BoardMenu from "@app/components/BoardMenu.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
|
import {makeLibraryPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -13,6 +15,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, board, selected, onclick}: Props = $props()
|
const {url, board, selected, onclick}: Props = $props()
|
||||||
|
|
||||||
|
const path = $derived(makeLibraryPath(url, board.address()))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative w-56 shrink-0">
|
<div class="relative w-56 shrink-0">
|
||||||
|
|
@ -22,7 +26,8 @@
|
||||||
class={cx("card card-sm card-interactive flex h-full w-full flex-col items-start gap-1 pr-10", {
|
class={cx("card card-sm card-interactive flex h-full w-full flex-col items-start gap-1 pr-10", {
|
||||||
"card-primary": selected,
|
"card-primary": selected,
|
||||||
})}>
|
})}>
|
||||||
<strong class="truncate w-full text-left">{board.title() || "Untitled shelf"}</strong>
|
<strong class="truncate w-full text-left"
|
||||||
|
><UnreadDot {path} class="mr-1" />{board.title() || "Untitled shelf"}</strong>
|
||||||
{#if board.description()}
|
{#if board.description()}
|
||||||
<span class="line-clamp-2 text-left text-sm opacity-70">{board.description()}</span>
|
<span class="line-clamp-2 text-left text-sm opacity-70">{board.description()}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import {makePollPath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
const {url, event, showRoom, showActivity, context}: Props = $props()
|
const {url, event, showRoom, showActivity, context}: Props = $props()
|
||||||
|
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
const path = makePollPath(url, event.id)
|
|
||||||
|
|
||||||
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})
|
||||||
|
|
||||||
|
|
@ -44,7 +43,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Poll" />
|
<EventActions {url} {event} noun="Poll" />
|
||||||
</ThunkStatusOrDeleted>
|
</ThunkStatusOrDeleted>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import RoomLink from "@app/components/RoomLink.svelte"
|
import RoomLink from "@app/components/RoomLink.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import {makePollPath} from "@app/routes"
|
import {makePollPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -17,12 +18,12 @@
|
||||||
|
|
||||||
const {url, event, context}: Props = $props()
|
const {url, event, context}: Props = $props()
|
||||||
|
|
||||||
|
const path = makePollPath(url, event.id)
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
class="cv flex flex-col gap-2 card card-interactive w-full"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makePollPath(url, event.id)}>
|
|
||||||
<NoteContent {event} {url} />
|
<NoteContent {event} {url} />
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<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">
|
<span class="whitespace-nowrap py-1 text-sm opacity-75">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import {makeThreadPath, makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
const {url, event, showRoom, showActivity, context}: Props = $props()
|
const {url, event, showRoom, showActivity, context}: Props = $props()
|
||||||
|
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
const path = makeThreadPath(url, event.id)
|
|
||||||
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url})
|
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url})
|
||||||
|
|
||||||
const createReaction = (values: EventContent) => publishReaction(event, values, {url})
|
const createReaction = (values: EventContent) => publishReaction(event, values, {url})
|
||||||
|
|
@ -43,7 +42,7 @@
|
||||||
{createReaction}
|
{createReaction}
|
||||||
reactionClass="tip-left" />
|
reactionClass="tip-left" />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {path} {event} {context} />
|
<EventActivity {event} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
<EventActions {url} {event} noun="Thread" />
|
<EventActions {url} {event} noun="Thread" />
|
||||||
</ThunkStatusOrDeleted>
|
</ThunkStatusOrDeleted>
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,11 @@
|
||||||
import {filter, formatTimestamp, max, spec} from "@welshman/lib"
|
import {filter, formatTimestamp, max, spec} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {COMMENT, tagSpec, tagValue} from "@welshman/util"
|
import {COMMENT, tagSpec, tagValue} from "@welshman/util"
|
||||||
import {fade} from "@lib/transition"
|
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import {notifications} from "@app/notifications"
|
|
||||||
import {makeThreadPath} from "@app/routes"
|
import {makeThreadPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -30,17 +29,13 @@
|
||||||
const onClick = () => goto(path)
|
const onClick = () => goto(path)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet unread()}
|
|
||||||
{#if $notifications.has(path)}
|
|
||||||
<div class="mr-1 inline-block h-2 w-2 rounded-full bg-primary" transition:fade></div>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#if mobile}
|
{#if mobile}
|
||||||
<Link
|
<Link
|
||||||
href={path}
|
href={path}
|
||||||
class="cv hover:bg-surface-less flex w-full flex-col gap-2 border-b border-solid border-line px-4 py-3 text-left text-sm transition-colors">
|
class="cv hover:bg-surface-less flex w-full flex-col gap-2 border-b border-solid border-line px-4 py-3 text-left text-sm transition-colors">
|
||||||
<p class="truncate font-medium">{@render unread()}{title || "Untitled thread"}</p>
|
<p class="truncate font-medium">
|
||||||
|
<UnreadDot {path} class="mr-1" />{title || "Untitled thread"}
|
||||||
|
</p>
|
||||||
<div class="text-content-muted flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
<div class="text-content-muted flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||||
<span class="flex min-w-0 items-center gap-1.5">
|
<span class="flex min-w-0 items-center gap-1.5">
|
||||||
<ProfileCircle pubkey={event.pubkey} {url} size={4} />
|
<ProfileCircle pubkey={event.pubkey} {url} size={4} />
|
||||||
|
|
@ -57,7 +52,7 @@
|
||||||
onclick={onClick}
|
onclick={onClick}
|
||||||
class="hover:bg-surface-less cursor-pointer border-b border-solid border-line text-sm transition-colors">
|
class="hover:bg-surface-less cursor-pointer border-b border-solid border-line text-sm transition-colors">
|
||||||
<td class="max-w-0 truncate px-4 py-3 align-top">
|
<td class="max-w-0 truncate px-4 py-3 align-top">
|
||||||
{@render unread()}{title || "Untitled thread"}
|
<UnreadDot {path} class="mr-1" />{title || "Untitled thread"}
|
||||||
</td>
|
</td>
|
||||||
<td class="w-32 px-4 py-3 align-middle">
|
<td class="w-32 px-4 py-3 align-middle">
|
||||||
<div class="flex min-w-0 items-center gap-2">
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import ThreadActions from "@app/components/ThreadActions.svelte"
|
import ThreadActions from "@app/components/ThreadActions.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import RoomLink from "@app/components/RoomLink.svelte"
|
import RoomLink from "@app/components/RoomLink.svelte"
|
||||||
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
import {makeThreadPath} from "@app/routes"
|
import {makeThreadPath} from "@app/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -18,13 +19,13 @@
|
||||||
|
|
||||||
const {url, event, context}: Props = $props()
|
const {url, event, context}: Props = $props()
|
||||||
|
|
||||||
|
const path = makeThreadPath(url, event.id)
|
||||||
const title = tagValue(tagSpec("title"), event.tags)
|
const title = tagValue(tagSpec("title"), event.tags)
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
class="cv flex flex-col gap-2 card card-interactive w-full"
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
href={makeThreadPath(url, event.id)}>
|
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="flex w-full items-center justify-between gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
<p class="text-xl">{title}</p>
|
<p class="text-xl">{title}</p>
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
Reply
|
Reply
|
||||||
</Button>
|
</Button>
|
||||||
{#if isComment}
|
{#if isComment}
|
||||||
<CommentActions segment="threads" {event} {url} {context} />
|
<CommentActions {event} {url} {context} />
|
||||||
{:else}
|
{:else}
|
||||||
<ThreadActions {event} {url} {context} />
|
<ThreadActions {event} {url} {context} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
16
src/app/components/UnreadDot.svelte
Normal file
16
src/app/components/UnreadDot.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import cx from "classnames"
|
||||||
|
import {fade} from "@lib/transition"
|
||||||
|
import {notifications} from "@app/notifications"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
path: string
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {path, class: className}: Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $notifications.has(path)}
|
||||||
|
<div class={cx("inline-block h-2 w-2 rounded-full bg-primary", className)} transition:fade></div>
|
||||||
|
{/if}
|
||||||
|
|
@ -1,18 +1,7 @@
|
||||||
import {derived, get, writable} from "svelte/store"
|
import {derived, get, writable} from "svelte/store"
|
||||||
import {Badge} from "@capawesome/capacitor-badge"
|
import {Badge} from "@capawesome/capacitor-badge"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {
|
import {assoc, first, identity, groupBy, now, remove, throttle, parseJson, gt} from "@welshman/lib"
|
||||||
assoc,
|
|
||||||
prop,
|
|
||||||
first,
|
|
||||||
identity,
|
|
||||||
groupBy,
|
|
||||||
now,
|
|
||||||
remove,
|
|
||||||
throttle,
|
|
||||||
parseJson,
|
|
||||||
gt,
|
|
||||||
} from "@welshman/lib"
|
|
||||||
import type {SignedEvent, TrustedEvent} from "@welshman/util"
|
import type {SignedEvent, TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
getIdOrAddress,
|
getIdOrAddress,
|
||||||
|
|
@ -48,8 +37,6 @@ export const checked = withGetter(
|
||||||
|
|
||||||
export const getChecked = (key: string) => checked.get()[key]
|
export const getChecked = (key: string) => checked.get()[key]
|
||||||
|
|
||||||
export const deriveChecked = (key: string) => derived(checked, prop<number>(key))
|
|
||||||
|
|
||||||
export const setChecked = (key: string) => checked.update(assoc(key, now()))
|
export const setChecked = (key: string) => checked.update(assoc(key, now()))
|
||||||
|
|
||||||
/** Room path while video call UI hides chat; checked + badge stay active until chat is shown. */
|
/** Room path while video call UI hides chat; checked + badge stay active until chat is shown. */
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
<NoteCard event={reply} {url} class="card z-feature w-full">
|
<NoteCard event={reply} {url} class="card z-feature w-full">
|
||||||
<div class="flex flex-col gap-3 ml-12">
|
<div class="flex flex-col gap-3 ml-12">
|
||||||
<NoteContent showEntire event={reply} {url} />
|
<NoteContent showEntire event={reply} {url} />
|
||||||
<CommentActions segment="calendar" event={reply} {url} {context} />
|
<CommentActions event={reply} {url} {context} />
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
<NoteCard event={reply} {url} class="card z-feature w-full">
|
<NoteCard event={reply} {url} class="card z-feature w-full">
|
||||||
<div class="flex flex-col gap-3 ml-12">
|
<div class="flex flex-col gap-3 ml-12">
|
||||||
<NoteContent showEntire event={reply} {url} />
|
<NoteContent showEntire event={reply} {url} />
|
||||||
<CommentActions segment="classifieds" event={reply} {url} {context} />
|
<CommentActions event={reply} {url} {context} />
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
<NoteCard event={reply} {url} class="card z-feature w-full">
|
<NoteCard event={reply} {url} class="card z-feature w-full">
|
||||||
<div class="flex flex-col gap-3 ml-12">
|
<div class="flex flex-col gap-3 ml-12">
|
||||||
<NoteContent showEntire event={reply} {url} />
|
<NoteContent showEntire event={reply} {url} />
|
||||||
<CommentActions segment="goals" event={reply} {url} {context} />
|
<CommentActions event={reply} {url} {context} />
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
<NoteCard event={$event} {url} class="card z-feature w-full">
|
<NoteCard event={$event} {url} class="card z-feature w-full">
|
||||||
<div class="flex flex-col gap-3 ml-12 flex flex-col gap-3">
|
<div class="flex flex-col gap-3 ml-12 flex flex-col gap-3">
|
||||||
<NoteContent showEntire event={$event} {url} />
|
<NoteContent showEntire event={$event} {url} />
|
||||||
<CommentActions segment="polls" showActivity {url} event={$event} {context} />
|
<CommentActions showActivity {url} event={$event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
{#if !showAll && $comments.length > 4}
|
{#if !showAll && $comments.length > 4}
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
<NoteCard event={reply} {url} class="card z-feature w-full">
|
<NoteCard event={reply} {url} class="card z-feature w-full">
|
||||||
<div class="flex flex-col gap-3 ml-12">
|
<div class="flex flex-col gap-3 ml-12">
|
||||||
<NoteContent showEntire event={reply} {url} />
|
<NoteContent showEntire event={reply} {url} />
|
||||||
<CommentActions segment="polls" event={reply} {url} {context} />
|
<CommentActions event={reply} {url} {context} />
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue