Add the ability to save events to the library
This commit is contained in:
parent
61a8c44750
commit
ff5d7aeb87
25 changed files with 207 additions and 126 deletions
|
|
@ -52,14 +52,13 @@
|
|||
<EmojiButton {onEmoji} class="button button-neutral button-xs join-item">
|
||||
<Icon icon={SmileCircle} size={4} />
|
||||
</EmojiButton>
|
||||
<Tippy
|
||||
bind:popover
|
||||
class="flex join-item button button-neutral button-xs"
|
||||
component={EventMenu}
|
||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
<Button onclick={showPopover}>
|
||||
<Button onclick={showPopover} class="flex join-item button button-neutral button-xs">
|
||||
<Tippy
|
||||
bind:popover
|
||||
component={EventMenu}
|
||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
<Icon icon={MenuDots} size={4} />
|
||||
</Button>
|
||||
</Tippy>
|
||||
</Tippy>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import {Router} from "@welshman/router"
|
||||
import {LOCALE, secondsToDate} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {displayRelayUrl, toNostrURI} from "@welshman/util"
|
||||
import {tracker} from "@welshman/app"
|
||||
import FileText from "@assets/icons/file-text.svg?dataurl"
|
||||
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
const {url, event}: Props = $props()
|
||||
|
||||
const relays = url ? [url] : Router.get().Event(event).getUrls()
|
||||
const nevent1 = nip19.neventEncode({...event, relays})
|
||||
const nevent1 = toNostrURI(nip19.neventEncode({...event, relays}))
|
||||
const seenOn = tracker.getRelays(event.id)
|
||||
const npub1 = nip19.npubEncode(event.pubkey)
|
||||
const json = JSON.stringify(event, null, 2)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import type {Snippet} from "svelte"
|
||||
import {goto} from "$app/navigation"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {COMMENT, ManagementMethod} from "@welshman/util"
|
||||
import {COMMENT, getAddress, ManagementMethod} from "@welshman/util"
|
||||
import {pubkey, repository, relaysByUrl, manageRelay} from "@welshman/app"
|
||||
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
||||
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
import Report from "@app/components/Report.svelte"
|
||||
import EventShare from "@app/components/EventShare.svelte"
|
||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||
import PinboardSelect from "@app/components/PinboardSelect.svelte"
|
||||
import {hasNip29} from "@app/relays"
|
||||
import {deriveUserIsSpaceAdmin} from "@app/members"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
|
@ -40,6 +42,8 @@
|
|||
|
||||
const showInfo = () => pushModal(EventInfo, {url, event})
|
||||
|
||||
const addToLibrary = () => pushModal(PinboardSelect, {url, address: getAddress(event)})
|
||||
|
||||
const share = async () => {
|
||||
if (hasNip29($relaysByUrl.get(url))) {
|
||||
pushModal(EventShare, {url, event})
|
||||
|
|
@ -79,6 +83,12 @@
|
|||
</script>
|
||||
|
||||
<ul class="menu whitespace-nowrap rounded-2xl bg-surface p-2" bind:this={ul}>
|
||||
<li>
|
||||
<Button onclick={showInfo}>
|
||||
<Icon size={4} icon={Code2} />
|
||||
{noun} Details
|
||||
</Button>
|
||||
</li>
|
||||
{#if isRoot}
|
||||
<li>
|
||||
<Button onclick={share}>
|
||||
|
|
@ -88,9 +98,9 @@
|
|||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
<Button onclick={showInfo}>
|
||||
<Icon size={4} icon={Code2} />
|
||||
{noun} Details
|
||||
<Button onclick={addToLibrary}>
|
||||
<Icon size={4} icon={GalleryWide} />
|
||||
Add to Library
|
||||
</Button>
|
||||
</li>
|
||||
{@render customActions?.()}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {goto} from "$app/navigation"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {preventDefault} from "@lib/html"
|
||||
|
|
@ -14,7 +13,7 @@
|
|||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
|
||||
import {roomsByUrl} from "@app/groups"
|
||||
import {makeRoomPath} from "@app/routes"
|
||||
|
||||
|
|
@ -40,13 +39,15 @@
|
|||
<ModalTitle>Share {noun}</ModalTitle>
|
||||
<ModalSubtitle>Which room would you like to share this event to?</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{#each $roomsByUrl.get(url) || [] as room (room.h)}
|
||||
<Button
|
||||
type="button"
|
||||
class={cx(`button button-${selection === room.h ? "primary" : "neutral"}`)}
|
||||
class="flex justify-center button {selection === room.h
|
||||
? 'button-primary'
|
||||
: 'button-neutral'}"
|
||||
onclick={() => toggleRoom(room.h)}>
|
||||
#<RoomName {...room} />
|
||||
<RoomNameWithImage {...room} />
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
type Props = {
|
||||
url: string
|
||||
address: string
|
||||
reference?: string
|
||||
}
|
||||
|
||||
const {url, address}: Props = $props()
|
||||
const {url, address, reference}: Props = $props()
|
||||
|
||||
const submit = async ({title, topics, value}: PinFormValues) => {
|
||||
const params = referenceToPin(value)
|
||||
|
|
@ -18,4 +19,10 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<PinForm {url} heading="Add Link" action="Add link" successMessage="Link added!" {submit} />
|
||||
<PinForm
|
||||
{url}
|
||||
heading="Add Link"
|
||||
action="Add link"
|
||||
successMessage="Link added!"
|
||||
values={{value: reference}}
|
||||
{submit} />
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
import StringMultiInput from "@lib/components/StringMultiInput.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import {pushToast} from "@app/toast"
|
||||
import {clearModals} from "@app/modal"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
|
|
@ -50,7 +51,7 @@
|
|||
pushToast({theme: "error", message: error})
|
||||
} else {
|
||||
pushToast({message: successMessage})
|
||||
back()
|
||||
clearModals()
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
|
|
|
|||
59
src/app/components/PinboardSelect.svelte
Normal file
59
src/app/components/PinboardSelect.svelte
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<script lang="ts">
|
||||
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import PinAdd from "@app/components/PinAdd.svelte"
|
||||
import {deriveBoards, addressToReference, type Board} from "@app/pinboards"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
address: string
|
||||
}
|
||||
|
||||
const {url, address}: Props = $props()
|
||||
|
||||
const boards = deriveBoards(url)
|
||||
const reference = addressToReference(address)
|
||||
|
||||
const selectBoard = (board: Board) => pushModal(PinAdd, {url, address: board.address, reference})
|
||||
</script>
|
||||
|
||||
<Modal class="flex flex-col gap-2">
|
||||
<ModalHeader>
|
||||
<ModalTitle>Add to Library</ModalTitle>
|
||||
<ModalSubtitle>on <RelayName {url} class="text-primary" /></ModalSubtitle>
|
||||
</ModalHeader>
|
||||
<ModalBody class="flex flex-col gap-2">
|
||||
{#if $boards.length === 0}
|
||||
<div class="flex flex-col items-center gap-4 py-8 text-center">
|
||||
<p class="opacity-70">This space doesn't have any shelves yet.</p>
|
||||
<Link href={makeSpacePath(url, "library")} class="button button-primary">
|
||||
Go to Library
|
||||
</Link>
|
||||
</div>
|
||||
{:else}
|
||||
{#each $boards as board (board.address)}
|
||||
<Button
|
||||
class="card card-sm card-interactive flex flex-row items-center justify-between gap-3"
|
||||
onclick={() => selectBoard(board)}>
|
||||
<span class="flex min-w-0 flex-col">
|
||||
<strong class="ellipsize">{board.title || "Untitled shelf"}</strong>
|
||||
{#if board.description}
|
||||
<span class="ellipsize text-sm opacity-70">{board.description}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<Icon size={4} icon={AltArrowRight} class="shrink-0" />
|
||||
</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {ManagementMethod} from "@welshman/util"
|
||||
import {ManagementMethod, getAddress} from "@welshman/util"
|
||||
import {pubkey, manageRelay, repository} from "@welshman/app"
|
||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||
import GalleryWide from "@assets/icons/gallery-wide.svg?dataurl"
|
||||
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||
import Danger from "@assets/icons/danger.svg?dataurl"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
import EventInfo from "@app/components/EventInfo.svelte"
|
||||
import Report from "@app/components/Report.svelte"
|
||||
import PinboardSelect from "@app/components/PinboardSelect.svelte"
|
||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
|
@ -25,6 +27,11 @@
|
|||
|
||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||
|
||||
const addToLibrary = () => {
|
||||
onClick()
|
||||
pushModal(PinboardSelect, {url, address: getAddress(event)})
|
||||
}
|
||||
|
||||
const report = () => {
|
||||
onClick()
|
||||
pushModal(Report, {url, event})
|
||||
|
|
@ -65,7 +72,13 @@
|
|||
<li>
|
||||
<Button onclick={showInfo}>
|
||||
<Icon size={4} icon={Code2} />
|
||||
Show JSON
|
||||
Message Details
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button onclick={addToLibrary}>
|
||||
<Icon size={4} icon={GalleryWide} />
|
||||
Add to Library
|
||||
</Button>
|
||||
</li>
|
||||
{#if event.pubkey === $pubkey}
|
||||
|
|
|
|||
|
|
@ -28,13 +28,12 @@
|
|||
|
||||
<svelte:document onmousemove={onMouseMove} />
|
||||
|
||||
<Tippy
|
||||
bind:popover
|
||||
component={RoomItemMenu}
|
||||
class="button button-xs button-neutral join-item"
|
||||
props={{url, event, onClick}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
<Button onclick={open}>
|
||||
<Button onclick={open} class="button button-xs button-neutral join-item">
|
||||
<Tippy
|
||||
bind:popover
|
||||
component={RoomItemMenu}
|
||||
props={{url, event, onClick}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
<Icon icon={MenuDots} size={4} />
|
||||
</Button>
|
||||
</Tippy>
|
||||
</Tippy>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
</div>
|
||||
{:else if $members.length === 0}
|
||||
<div class="card bg-surface p-4">
|
||||
<span class="text-muted">No members yet</span>
|
||||
<span class="text-content-muted">No members yet</span>
|
||||
</div>
|
||||
{:else}
|
||||
{#each $members as pubkey (pubkey)}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,9 @@
|
|||
const {url, h, ...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex grow items-center justify-between gap-4 {props.class}">
|
||||
<div class="flex items-center gap-2">
|
||||
<RoomImage {url} {h} />
|
||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||
<RoomName {url} {h} />
|
||||
</div>
|
||||
<div class="flex min-w-0 items-center gap-2 {props.class}">
|
||||
<RoomImage {url} {h} />
|
||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||
<RoomName {url} {h} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
<SecondaryNavItem href={path} title={roomName} {replaceState} {notification}>
|
||||
<RoomNameWithImage {url} {h} />
|
||||
{#if showDifferenceIcon}
|
||||
<Icon icon={$shouldNotifyForRoom ? Bell : BellOff} size={4} class="opacity-50" />
|
||||
<Icon icon={$shouldNotifyForRoom ? Bell : BellOff} size={4} class="ml-auto opacity-50" />
|
||||
{/if}
|
||||
</SecondaryNavItem>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
|
||||
import ThreadBoardItem from "@app/components/ThreadBoardItem.svelte"
|
||||
|
||||
type Props = {
|
||||
|
|
@ -13,27 +13,28 @@
|
|||
</script>
|
||||
|
||||
<section class="card card-flat overflow-hidden pb-4 p-0">
|
||||
<header class="flex gap-2 justify-between bg-surface px-4 py-2.5">
|
||||
<h2 class="text-sm font-bold">
|
||||
<header
|
||||
class="flex gap-2 items-center justify-between px-4 py-3 border-b border-line border-solid">
|
||||
<h2 class="text-lg">
|
||||
{#if h}
|
||||
#<RoomName {url} {h} />
|
||||
<RoomNameWithImage {url} {h} />
|
||||
{:else}
|
||||
General
|
||||
{/if}
|
||||
</h2>
|
||||
<span class="text-xs text-muted">
|
||||
<span class="text-content-muted text-sm">
|
||||
{threads.length}
|
||||
{threads.length === 1 ? "topic" : "topics"}
|
||||
{threads.length === 1 ? "Topic" : "Topics"}
|
||||
</span>
|
||||
</header>
|
||||
<table class="w-full border-collapse">
|
||||
<thead
|
||||
class="hidden text-xs font-bold uppercase tracking-wide text-muted sm:table-header-group">
|
||||
class="hidden text-xs font-bold uppercase tracking-wide text-content-muted sm:table-header-group border-b border-line border-solid bg-surface-less">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left">Topic</th>
|
||||
<th class="w-32 px-4 py-2 text-left">Author</th>
|
||||
<th class="w-20 px-4 py-2 text-center">Replies</th>
|
||||
<th class="w-32 px-4 py-2 text-right">Last post</th>
|
||||
<th class="px-4 py-3 text-left">Topic</th>
|
||||
<th class="w-32 px-4 py-3 text-left">Author</th>
|
||||
<th class="w-20 px-4 py-3 text-center">Replies</th>
|
||||
<th class="w-32 px-4 py-3 text-right">Last post</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {COMMENT, getTagValue} from "@welshman/util"
|
||||
import {goto} from "$app/navigation"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileName from "@app/components/ProfileName.svelte"
|
||||
import {deriveEventsForUrl} from "@app/repository"
|
||||
|
|
@ -25,21 +24,19 @@
|
|||
const goToThread = () => goto(path)
|
||||
</script>
|
||||
|
||||
<tr class="cursor-pointer transition-colors hover:bg-surface text-sm" onclick={goToThread}>
|
||||
<td class="px-4 py-2 align-top">
|
||||
<Link href={path} class="ellipsize font-semibold">
|
||||
{title || "Untitled thread"}
|
||||
</Link>
|
||||
<tr
|
||||
class="cursor-pointer transition-colors hover:bg-surface-less text-sm border-b border-solid border-line"
|
||||
onclick={goToThread}>
|
||||
<td class="px-4 py-3 align-top ellipsize">
|
||||
{title || "Untitled thread"}
|
||||
</td>
|
||||
<td class="px-4 py-2 align-middle">
|
||||
<div class="flex items-center gap-2">
|
||||
<ProfileCircle pubkey={event.pubkey} {url} size={5} />
|
||||
<span class="ellipsize">
|
||||
<ProfileName pubkey={event.pubkey} {url} />
|
||||
</span>
|
||||
</div>
|
||||
<td class="px-4 py-3 align-middle flex items-center gap-2">
|
||||
<ProfileCircle pubkey={event.pubkey} {url} size={5} />
|
||||
<span class="ellipsize">
|
||||
<ProfileName pubkey={event.pubkey} {url} />
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-2 align-middle text-right">
|
||||
<td class="px-4 py-3 align-middle text-right">
|
||||
{replyCount}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-2 align-middle text-right">
|
||||
|
|
|
|||
|
|
@ -138,9 +138,9 @@
|
|||
{:else if $callState === CallState.Connected}
|
||||
<span class="text-sm font-semibold text-success">Voice Connected</span>
|
||||
{:else}
|
||||
<span class="text-sm font-semibold text-muted">Disconnected</span>
|
||||
<span class="text-sm font-semibold text-content-muted">Disconnected</span>
|
||||
{/if}
|
||||
<span class="ellipsize text-xs text-muted">
|
||||
<span class="ellipsize text-xs text-content-muted">
|
||||
{roomName} / {spaceName}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import {
|
|||
getTagValue,
|
||||
matchFilters,
|
||||
readList,
|
||||
sortEventsDesc,
|
||||
} from "@welshman/util"
|
||||
import type {Filter, PublishedList, TrustedEvent} from "@welshman/util"
|
||||
import {load, mergeRepositoryUpdates, request} from "@welshman/net"
|
||||
|
|
@ -124,13 +123,8 @@ export const makeFeed = ({
|
|||
const events = writable<TrustedEvent[]>([])
|
||||
const seen = new Set<string>()
|
||||
|
||||
let interval = int(MONTH)
|
||||
let buffer = sortEventsDesc(
|
||||
uniqBy(
|
||||
e => e.id,
|
||||
relays.flatMap(url => Array.from(getEventsForUrl(url, filters))),
|
||||
),
|
||||
)
|
||||
let interval = int(6, MONTH)
|
||||
let buffer: TrustedEvent[] = []
|
||||
let backwardWindow = [at - interval, at]
|
||||
let forwardWindow = [at, at + interval]
|
||||
|
||||
|
|
@ -145,7 +139,7 @@ export const makeFeed = ({
|
|||
}
|
||||
|
||||
// Batch-insert events into the visible store with a single update
|
||||
const insertEvents = (newEvents: TrustedEvent[]) => {
|
||||
const insertEvents = (newEvents: Iterable<TrustedEvent>) => {
|
||||
const visible: TrustedEvent[] = []
|
||||
|
||||
for (const event of newEvents) {
|
||||
|
|
@ -153,10 +147,9 @@ export const makeFeed = ({
|
|||
continue
|
||||
}
|
||||
|
||||
seen.add(event.id)
|
||||
|
||||
if (between([backwardWindow[0], forwardWindow[1]], event.created_at)) {
|
||||
visible.push(event)
|
||||
seen.add(event.id)
|
||||
} else {
|
||||
insertIntoBuffer(event)
|
||||
}
|
||||
|
|
@ -274,6 +267,10 @@ export const makeFeed = ({
|
|||
},
|
||||
})
|
||||
|
||||
for (const url of relays) {
|
||||
insertEvents(getEventsForUrl(url, filters))
|
||||
}
|
||||
|
||||
return {
|
||||
events,
|
||||
cleanup: () => {
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ export const deleteBoard = (url: string, address: string) =>
|
|||
]),
|
||||
)
|
||||
|
||||
export const pinToReference = (pin: Pin): string => {
|
||||
const [type, data = "", relay] = pin.value
|
||||
const valueToReference = (value: string[]): string => {
|
||||
const [type, data = "", relay] = value
|
||||
const relays = relay ? [relay] : []
|
||||
|
||||
if (type === "e") return nip19.neventEncode({id: data, relays})
|
||||
|
|
@ -181,6 +181,10 @@ export const pinToReference = (pin: Pin): string => {
|
|||
return data
|
||||
}
|
||||
|
||||
export const pinToReference = (pin: Pin): string => valueToReference(pin.value)
|
||||
|
||||
export const addressToReference = (address: string): string => valueToReference(["a", address])
|
||||
|
||||
export const referenceToPin = (reference: string): Partial<Pin> | undefined => {
|
||||
const trimmed = reference.trim()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
const {children, ...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2 p-2 text-xs uppercase text-subtle">
|
||||
<div class="flex items-center gap-2 p-2 text-xs uppercase text-content-subtle">
|
||||
<div class="h-px grow" style="background-color: var(--line)"></div>
|
||||
{#if children}
|
||||
<p>{@render children?.()}</p>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{#if props.info}
|
||||
<p class="text-sm text-muted">
|
||||
<p class="text-sm text-content-muted">
|
||||
{@render props.info()}
|
||||
</p>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
ondrop={stopPropagation(preventDefault(onDrop))}>
|
||||
<div class="flex flex-col items-center gap-2 text-center">
|
||||
<Icon icon={GallerySend} size={8} />
|
||||
<p class="text-muted text-sm">Drag and drop images or click to select</p>
|
||||
<p class="text-content-muted text-sm">Drag and drop images or click to select</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@
|
|||
const {children}: Props = $props()
|
||||
</script>
|
||||
|
||||
<p class="text-muted text-center text-sm">{@render children()}</p>
|
||||
<p class="text-content-muted text-center text-sm">{@render children()}</p>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@
|
|||
.input:focus-within,
|
||||
.tiptap:focus,
|
||||
.tiptap:focus-within {
|
||||
@apply outline-none;
|
||||
border: 1px solid var(--primary) !important;
|
||||
@apply border-primary outline-none;
|
||||
box-shadow:
|
||||
0 0 0 4px color-mix(in srgb, var(--primary) 18%, transparent),
|
||||
inset 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@
|
|||
onclick={() => openSpace(url)}>
|
||||
<div class="flex w-full items-start gap-2">
|
||||
<div
|
||||
class="mt-4 flex cursor-grab p-1 text-subtle transition-colors group-hover:text-muted">
|
||||
class="mt-4 flex cursor-grab p-1 text-content-subtle transition-colors group-hover:text-content-muted">
|
||||
<Icon icon={DragHandle} />
|
||||
</div>
|
||||
<RelaySummary hideFavorites {url} />
|
||||
|
|
|
|||
|
|
@ -128,23 +128,21 @@
|
|||
</SpaceBar>
|
||||
|
||||
<PageContent class="flex flex-col gap-4 p-4">
|
||||
<div class="card flex flex-col gap-2">
|
||||
<label class="input input-sm input-group flex w-full items-center gap-2">
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
<input
|
||||
bind:value={term}
|
||||
class="min-w-0 grow"
|
||||
type="text"
|
||||
placeholder="Search people or roles..." />
|
||||
</label>
|
||||
{#if visibleMembers.length === 0}
|
||||
<p class="flex flex-col items-center py-20 text-center">No members found.</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{#each visibleMembers as { pubkey, roleList } (pubkey)}
|
||||
<SpaceMember {url} {pubkey} roles={roleList} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="input input-group flex w-full items-center gap-2 card">
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
<input
|
||||
bind:value={term}
|
||||
class="min-w-0 grow"
|
||||
type="text"
|
||||
placeholder="Search people or roles..." />
|
||||
</label>
|
||||
{#if visibleMembers.length === 0}
|
||||
<p class="flex flex-col items-center py-20 text-center">No members found.</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{#each visibleMembers as { pubkey, roleList } (pubkey)}
|
||||
<SpaceMember {url} {pubkey} roles={roleList} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
|
|
|||
|
|
@ -86,22 +86,20 @@
|
|||
</SpaceBar>
|
||||
|
||||
<PageContent class="flex flex-col gap-4 p-4">
|
||||
<div class="card flex flex-col gap-2">
|
||||
<label class="input input-sm input-group flex w-full items-center gap-2">
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
<input bind:value={term} class="min-w-0 grow" type="text" placeholder="Search library..." />
|
||||
</label>
|
||||
<Masonry items={filtered} getKey={board => board.address} columnWidth={80} gap={2}>
|
||||
{#snippet child(board)}
|
||||
<PinboardItem {url} {board} />
|
||||
{/snippet}
|
||||
</Masonry>
|
||||
{#if loading}
|
||||
<p class="flex items-center justify-center py-20">
|
||||
<Spinner loading>Loading library...</Spinner>
|
||||
</p>
|
||||
{:else if filtered.length === 0}
|
||||
<p class="flex flex-col items-center py-20 text-center">No shelves found.</p>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="card input input-group flex w-full items-center gap-2">
|
||||
<Icon size={4} icon={Magnifier} />
|
||||
<input bind:value={term} class="min-w-0 grow" type="text" placeholder="Search library..." />
|
||||
</label>
|
||||
<Masonry items={filtered} getKey={board => board.address} columnWidth={80} gap={4}>
|
||||
{#snippet child(board)}
|
||||
<PinboardItem {url} {board} />
|
||||
{/snippet}
|
||||
</Masonry>
|
||||
{#if loading}
|
||||
<p class="flex items-center justify-center py-20">
|
||||
<Spinner loading>Loading library...</Spinner>
|
||||
</p>
|
||||
{:else if filtered.length === 0}
|
||||
<p class="flex flex-col items-center py-20 text-center">No shelves found.</p>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
|
|
|||
Loading…
Reference in a new issue