Clean up convention violations, dead code, and daisyUI leftovers
This commit is contained in:
parent
c0c8354a76
commit
a2c5a8e2ff
38 changed files with 120 additions and 224 deletions
|
|
@ -6,7 +6,7 @@ Flotilla is a Nostr "relays as groups" community chat client. It implements NIP-
|
|||
|
||||
- SvelteKit 5.48+ with TypeScript 5.9+
|
||||
- Capacitor for cross-platform (Web/PWA, Android, iOS)
|
||||
- TailwindCSS + DaisyUI for styling
|
||||
- TailwindCSS for styling
|
||||
- Welshman library suite for Nostr protocol
|
||||
- IndexedDB for local storage
|
||||
- Vite for building
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export default [
|
|||
parser: ts.parser,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// eslint doesn't see type parameters declared by <script generics="...">
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["build/", ".svelte-kit/", "dist/"],
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
"postcss": "^8.5.15",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-svelte": "^3.4.1",
|
||||
"sharp": "0.35.0-rc.0",
|
||||
"sharp": "^0.35.3",
|
||||
"svelte": "^5.55.9",
|
||||
"svelte-check": "^4.3.5",
|
||||
"tailwindcss": "^4.2.2",
|
||||
|
|
|
|||
BIN
pnpm-lock.yaml
BIN
pnpm-lock.yaml
Binary file not shown.
|
|
@ -16,4 +16,4 @@ minimumReleaseAgeExclude:
|
|||
- '@welshman/store'
|
||||
- '@welshman/util'
|
||||
overrides:
|
||||
sharp: 0.35.0-rc.0
|
||||
sharp: ^0.35.3
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
import {derived, get, writable, type Readable} from "svelte/store"
|
||||
import {
|
||||
dissoc,
|
||||
filterVals,
|
||||
fromPairs,
|
||||
isDefined,
|
||||
last,
|
||||
poll,
|
||||
randomId,
|
||||
sleep,
|
||||
tryCatch,
|
||||
} from "@welshman/lib"
|
||||
import {AuthStatus, load, Pool, request, SocketStatus} from "@welshman/net"
|
||||
import {dissoc, fromPairs, last, poll, randomId, sleep, tryCatch} from "@welshman/lib"
|
||||
import {AuthStatus, Pool, request, SocketStatus} from "@welshman/net"
|
||||
import {
|
||||
displayRelayUrl,
|
||||
getTagValue,
|
||||
|
|
@ -42,8 +32,6 @@ export type InviteData = {
|
|||
code?: string
|
||||
}
|
||||
|
||||
export type InviteCreateStatus = "loading" | "network" | "auth" | "failed" | "noclaim" | "ready"
|
||||
|
||||
export type JoinRequestParams = {
|
||||
url: string
|
||||
claim: string
|
||||
|
|
@ -116,21 +104,6 @@ export const deriveRelayAuthError = (url: string) =>
|
|||
}
|
||||
})
|
||||
|
||||
export const requestRelayClaim = async (url: string) => {
|
||||
const filters = [{kinds: [RELAY_INVITE], limit: 1}]
|
||||
const events = await load({filters, relays: [url]})
|
||||
|
||||
if (events.length > 0) {
|
||||
return getTagValue("claim", events[0].tags)
|
||||
}
|
||||
}
|
||||
|
||||
export const requestRelayClaims = async (urls: string[]) =>
|
||||
filterVals(
|
||||
isDefined,
|
||||
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
||||
)
|
||||
|
||||
export const makeJoinRequest = (params: JoinRequestParams) =>
|
||||
makeEvent(RELAY_JOIN, {tags: [["claim", params.claim]]})
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ import {pushToast} from "@app/toast"
|
|||
|
||||
export const LIVEKIT_PARTICIPANTS = 39004
|
||||
|
||||
export {checkRelayHasLivekit} from "$lib/livekit"
|
||||
|
||||
export {supportsAudioOutputSelection}
|
||||
|
||||
const LIVEKIT_DEFAULT_DEVICE_ID = "default"
|
||||
|
|
@ -105,15 +103,6 @@ export const isParticipantSpeaking = derived(
|
|||
$participants.some(sp => participantKey(sp) === participantKey(p)),
|
||||
)
|
||||
|
||||
export const isLocalSpeaking = derived(
|
||||
[currentCallSession, speakingParticipants],
|
||||
([$session, $speaking]) => {
|
||||
if (!$session?.livekit) return false
|
||||
const local = participantFromLiveKitIdentity($session.livekit.localParticipant.identity)
|
||||
return $speaking.some(sp => participantKey(sp) === participantKey(local))
|
||||
},
|
||||
)
|
||||
|
||||
export const videoTrackRevision = writable(0)
|
||||
|
||||
export const triggerVideoTrackRevision = () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import cx from "classnames"
|
||||
import {page} from "$app/stores"
|
||||
import {remove, uniq, formatTimestamp} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
|
|
@ -35,8 +36,11 @@
|
|||
|
||||
<Button class="flex flex-col justify-start gap-1 w-full" onclick={openChat}>
|
||||
<div
|
||||
class="cursor-pointer border-t border-solid px-3 py-2 transition-colors hover:bg-surface {props.class}"
|
||||
class:bg-surface={active}
|
||||
class={cx(
|
||||
"cursor-pointer border-t border-solid px-3 py-2 transition-colors hover:bg-surface",
|
||||
props.class,
|
||||
{"bg-surface": active},
|
||||
)}
|
||||
style="border-color: var(--line)">
|
||||
<div class="flex flex-col justify-start gap-1">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
}
|
||||
|
||||
const {pubkeys}: Props = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
|
|
@ -28,6 +30,6 @@
|
|||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -72,8 +72,7 @@
|
|||
{/if}
|
||||
<div
|
||||
data-event={event.id}
|
||||
class="group flex items-center justify-end gap-1 px-2"
|
||||
class:flex-row-reverse={!isOwn}>
|
||||
class={cx("group flex items-center justify-end gap-1 px-2", {"flex-row-reverse": !isOwn})}>
|
||||
{#if !isMobile}
|
||||
<Tippy
|
||||
bind:popover
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
const copyLink = () => clip(nevent1)
|
||||
const copyPubkey = () => clip(npub1)
|
||||
const copyJson = () => clip(json)
|
||||
const back = () => history.back()
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(LOCALE, {
|
||||
dateStyle: "long",
|
||||
|
|
@ -110,6 +111,6 @@
|
|||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary grow" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary grow" onclick={back}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import {PLATFORM_NAME} from "@app/env"
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
|
@ -37,5 +39,5 @@
|
|||
href="https://nostrapps.com#signers">nostrapps.com</Link
|
||||
>.
|
||||
</p>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalBody></Modal>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import {PLATFORM_NAME} from "@app/env"
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
|
@ -27,5 +29,5 @@
|
|||
class="underline">nostr.how</Link
|
||||
>.
|
||||
</p>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalBody></Modal>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
|
@ -28,5 +30,5 @@
|
|||
To learn more about how to manage your keys, or to set up an account, try
|
||||
<Link external class="link" href="https://nosta.me/">nosta.me</Link>.
|
||||
</p>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalBody></Modal>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
import Modal from "@lib/components/Modal.svelte"
|
||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||
import {PLATFORM_NAME} from "@app/env"
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
|
|
@ -27,6 +29,6 @@
|
|||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -88,10 +88,7 @@
|
|||
Title
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input
|
||||
bind:value={title}
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Optional title" />
|
||||
<input bind:value={title} class="input w-full" placeholder="Optional title" />
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
|
|
@ -133,7 +130,7 @@
|
|||
<p>A URL or nostr link (note, nevent, naddr, npub, or nprofile).</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input bind:value class="input input-bordered w-full" placeholder="URL or nevent..." />
|
||||
<input bind:value class="input w-full" placeholder="URL or nevent..." />
|
||||
{/snippet}
|
||||
</Field>
|
||||
</ModalBody>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
Title
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<input bind:value={title} class="input input-bordered w-full" placeholder="Shelf title" />
|
||||
<input bind:value={title} class="input w-full" placeholder="Shelf title" />
|
||||
{/snippet}
|
||||
</Field>
|
||||
<Field>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
{#snippet input()}
|
||||
<input
|
||||
bind:value={description}
|
||||
class="input input-bordered w-full"
|
||||
class="input w-full"
|
||||
placeholder="What's this shelf about?" />
|
||||
{/snippet}
|
||||
</Field>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
import {canEnforceNip70} from "@app/relays"
|
||||
import {makePollPath, makeSpacePath} from "@app/routes"
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
url: string
|
||||
event: TrustedEvent
|
||||
showRoom?: boolean
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import cx from "classnames"
|
||||
import {userProfile} from "@welshman/app"
|
||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
)
|
||||
</script>
|
||||
|
||||
<div class="primary-nav" class:justify-between={PLATFORM_RELAYS.length === 0}>
|
||||
<div class={cx("primary-nav", {"justify-between": PLATFORM_RELAYS.length === 0})}>
|
||||
<PrimaryNavSpaces />
|
||||
{#if PLATFORM_RELAYS.length > 0}
|
||||
<Divider />
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
const copyLink = () => clip(nprofile1)
|
||||
const copyPubkey = () => clip(npub1)
|
||||
const copyJson = () => clip(json)
|
||||
const back = () => history.back()
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(LOCALE, {
|
||||
dateStyle: "long",
|
||||
|
|
@ -116,6 +117,6 @@
|
|||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary grow" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary grow" onclick={back}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
}
|
||||
|
||||
const {subtitle = "", pubkeys, url, ...restProps}: Props = $props()
|
||||
|
||||
const back = () => history.back()
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
|
|
@ -31,6 +33,6 @@
|
|||
{/each}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary" onclick={() => history.back()}>Got it</Button>
|
||||
<Button class="button button-primary" onclick={back}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -515,8 +515,9 @@
|
|||
{#if type === "new-messages"}
|
||||
<div
|
||||
{id}
|
||||
class="flex items-center py-2 text-xs transition-colors"
|
||||
class:opacity-0={showFixedNewMessages}>
|
||||
class={cx("flex items-center py-2 text-xs transition-colors", {
|
||||
"opacity-0": showFixedNewMessages,
|
||||
})}>
|
||||
<div class="h-px grow bg-primary text-primary-content"></div>
|
||||
<p
|
||||
class="rounded-full bg-primary text-primary-content px-2 py-1"
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@
|
|||
</Tippy>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="chat-editor grow overflow-hidden rounded-box">
|
||||
<div class="chat-editor grow overflow-hidden">
|
||||
<EditorContent {autofocus} {editor} />
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -161,8 +161,10 @@
|
|||
<strong class="flex items-center gap-1 relative">
|
||||
<RelayName {url} class="truncate min-w-0" />
|
||||
<div
|
||||
class="absolute -right-3 top-0 h-2 w-2 rounded-full bg-primary text-primary-content transition-all opacity-0"
|
||||
class:opacity-100={$userIsAdmin && $actionItems.length > 0}>
|
||||
class={cx(
|
||||
"absolute -right-3 top-0 h-2 w-2 rounded-full bg-primary text-primary-content transition-all",
|
||||
$userIsAdmin && $actionItems.length > 0 ? "opacity-100" : "opacity-0",
|
||||
)}>
|
||||
</div>
|
||||
{#if $notificationSettings.push && !$shouldNotify}
|
||||
<Icon icon={BellOff} size={3} class="opacity-50" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {parse, renderAsHtml} from "@welshman/content"
|
||||
import Close from "@assets/icons/close.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
|
|
@ -84,16 +85,16 @@
|
|||
{#key $toast.id}
|
||||
<div
|
||||
role="alert"
|
||||
class="card relative flex justify-center whitespace-normal text-left"
|
||||
class:text-content={theme === "info"}
|
||||
class:text-error={theme === "error"}
|
||||
style={theme === "error" ? "border-color: var(--error)" : ""}>
|
||||
class={cx("card relative flex justify-center whitespace-normal text-left", {
|
||||
"text-content": theme === "info",
|
||||
"border-error text-error": theme === "error",
|
||||
})}>
|
||||
<Button
|
||||
class="button button-neutral button-xs button-circle absolute -top-2 -right-2 hidden md:inline-flex flex justify-center items-center"
|
||||
onclick={onClose}>
|
||||
<Icon icon={Close} size={4} />
|
||||
</Button>
|
||||
<p class="md:pr-6" class:welshman-content-error={theme === "error"}>
|
||||
<p class={cx("md:pr-6", {"welshman-content-error": theme === "error"})}>
|
||||
{#if $toast.message}
|
||||
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||
{#if $toast.action}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
<span
|
||||
class="pointer-events-none absolute bottom-1 left-1 max-w-[calc(100%-0.5rem)] truncate rounded bg-base-100/80 px-1.5 py-0.5 text-xs">
|
||||
class="pointer-events-none absolute bottom-1 left-1 max-w-[calc(100%-0.5rem)] truncate rounded bg-surface/80 px-1.5 py-0.5 text-xs">
|
||||
{labelFor(tile.liveKitIdentity, tile.source)}{tile.isLocal ? " (you)" : ""}
|
||||
</span>
|
||||
{#if videoTiles.length > 1}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,9 @@
|
|||
import {deriveItems, deriveItemsByKey, getter, makeDeriveItem, makeLoadItem} from "@welshman/store"
|
||||
import {
|
||||
ensurePlaintext,
|
||||
makeOutboxLoader,
|
||||
makeUserData,
|
||||
makeUserLoader,
|
||||
repository,
|
||||
tracker,
|
||||
} from "@welshman/app"
|
||||
import {derived, writable} from "svelte/store"
|
||||
import {repository, tracker} from "@welshman/app"
|
||||
import {writable} from "svelte/store"
|
||||
import {
|
||||
batch,
|
||||
between,
|
||||
call,
|
||||
groupBy,
|
||||
insertAt,
|
||||
int,
|
||||
now,
|
||||
|
|
@ -22,87 +13,13 @@ import {
|
|||
MONTH,
|
||||
YEAR,
|
||||
} from "@welshman/lib"
|
||||
import {
|
||||
Address,
|
||||
EVENT_TIME,
|
||||
FEED,
|
||||
FEEDS,
|
||||
asDecryptedEvent,
|
||||
getAddress,
|
||||
getIdFilters,
|
||||
getTagValue,
|
||||
matchFilters,
|
||||
readList,
|
||||
} from "@welshman/util"
|
||||
import type {Filter, PublishedList, TrustedEvent} from "@welshman/util"
|
||||
import {load, mergeRepositoryUpdates, request} from "@welshman/net"
|
||||
import {EVENT_TIME, getAddress, getTagValue, matchFilters} from "@welshman/util"
|
||||
import type {Filter, TrustedEvent} from "@welshman/util"
|
||||
import {mergeRepositoryUpdates, request} from "@welshman/net"
|
||||
import type {RepositoryUpdate} from "@welshman/net"
|
||||
import {Router} from "@welshman/router"
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import {readFeed} from "@lib/feeds"
|
||||
import {getEventsForUrl} from "@app/repository"
|
||||
export const feedsByAddress = deriveItemsByKey({
|
||||
repository,
|
||||
getKey: feed => getAddress(feed.event),
|
||||
filters: [{kinds: [FEED]}],
|
||||
eventToItem: readFeed,
|
||||
})
|
||||
|
||||
export const getFeedsByAddress = getter(feedsByAddress)
|
||||
|
||||
export const feeds = deriveItems(feedsByAddress)
|
||||
|
||||
export const getFeeds = getter(feeds)
|
||||
|
||||
export const getFeed = (address: string) => getFeedsByAddress().get(address)
|
||||
|
||||
export const fetchFeed = (address: string) => {
|
||||
const {pubkey} = Address.from(address)
|
||||
|
||||
return load({
|
||||
relays: Router.get().FromPubkey(pubkey).getUrls(),
|
||||
filters: getIdFilters([address]),
|
||||
})
|
||||
}
|
||||
|
||||
export const loadFeed = makeLoadItem(fetchFeed, getFeed)
|
||||
|
||||
export const deriveFeed = makeDeriveItem(feedsByAddress, loadFeed)
|
||||
|
||||
export const feedsByPubkey = derived(feeds, $feeds => groupBy(f => f.event.pubkey, $feeds))
|
||||
|
||||
export const getFeedsByPubkey = getter(feedsByPubkey)
|
||||
|
||||
export const getFeedsForPubkey = (pubkey: string) => getFeedsByPubkey().get(pubkey)
|
||||
|
||||
export const loadFeedsForPubkey = makeLoadItem(makeOutboxLoader(FEED), getFeedsForPubkey)
|
||||
|
||||
export const userFeeds = makeUserData(feedsByPubkey, loadFeedsForPubkey)
|
||||
|
||||
export const loadUserFeeds = makeUserLoader(loadFeedsForPubkey)
|
||||
|
||||
export const feedFavoritesByPubkey = deriveItemsByKey<PublishedList>({
|
||||
repository,
|
||||
getKey: list => list.event.pubkey,
|
||||
filters: [{kinds: [FEEDS]}],
|
||||
eventToItem: async event =>
|
||||
readList(
|
||||
asDecryptedEvent(event, {
|
||||
content: await ensurePlaintext(event),
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
export const getFeedFavoritesByPubkey = getter(feedFavoritesByPubkey)
|
||||
|
||||
export const getFeedFavorites = (pubkey: string) => getFeedFavoritesByPubkey().get(pubkey)
|
||||
|
||||
export const loadFeedFavorites = makeLoadItem(makeOutboxLoader(FEEDS), getFeedFavorites)
|
||||
|
||||
export const userFeedFavorites = makeUserData(feedFavoritesByPubkey, loadFeedFavorites)
|
||||
|
||||
export const loadUserFeedFavorites = makeUserLoader(loadFeedFavorites)
|
||||
|
||||
export const makeFeed = ({
|
||||
relays,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
getTagValues,
|
||||
sortEventsAsc,
|
||||
} from "@welshman/util"
|
||||
import type {Filter, PublishedRoomMeta, TrustedEvent} from "@welshman/util"
|
||||
import type {Filter, ManagementRequest, PublishedRoomMeta, TrustedEvent} from "@welshman/util"
|
||||
import {first, simpleCache, sortBy, spec, uniq} from "@welshman/lib"
|
||||
import {addRoomMember, manageRelay, pubkey, waitForThunkError} from "@welshman/app"
|
||||
import {load} from "@welshman/net"
|
||||
|
|
@ -47,7 +47,7 @@ const DEFAULT_LIGHTNESS = 0.5
|
|||
|
||||
// Parse the hue from a ["color", hue] tag, falling back to 0.
|
||||
export const parseRoleColor = (tags: string[][]): number => {
|
||||
const hue = parseFloat(first(getTags("color", tags))?.[1] ?? "")
|
||||
const hue = parseFloat(getTagValue("color", tags) ?? "")
|
||||
|
||||
return isNaN(hue) ? 0 : hue
|
||||
}
|
||||
|
|
@ -105,6 +105,11 @@ export const deriveSpaceMemberRoles = (url: string) =>
|
|||
const reloadRole = (url: string, id: string) =>
|
||||
load({relays: [url], filters: [{kinds: [RELAY_ROLE], "#d": [id]}]})
|
||||
|
||||
// Roles extend NIP-86: these methods aren't in welshman's ManagementMethod, and hue and
|
||||
// order go over the wire as JSON numbers, not strings, per the relay's implementation.
|
||||
const manageRole = (url: string, method: string, params: (string | number)[]) =>
|
||||
manageRelay(url, {method, params} as ManagementRequest)
|
||||
|
||||
export const createRole = async (
|
||||
url: string,
|
||||
id: string,
|
||||
|
|
@ -113,11 +118,7 @@ export const createRole = async (
|
|||
color: number,
|
||||
order: number,
|
||||
): Promise<string | undefined> => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: "createrole" as ManagementMethod,
|
||||
// hue and order go over the wire as JSON numbers, not strings, per the relay's NIP-86 impl.
|
||||
params: [id, label, description, color, order] as unknown as string[],
|
||||
})
|
||||
const {error} = await manageRole(url, "createrole", [id, label, description, color, order])
|
||||
|
||||
if (!error) {
|
||||
await reloadRole(url, id)
|
||||
|
|
@ -134,11 +135,7 @@ export const editRole = async (
|
|||
color: number,
|
||||
order: number,
|
||||
): Promise<string | undefined> => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: "editrole" as ManagementMethod,
|
||||
// hue and order go over the wire as JSON numbers, not strings, per the relay's NIP-86 impl.
|
||||
params: [id, label, description, color, order] as unknown as string[],
|
||||
})
|
||||
const {error} = await manageRole(url, "editrole", [id, label, description, color, order])
|
||||
|
||||
if (!error) {
|
||||
await reloadRole(url, id)
|
||||
|
|
@ -148,10 +145,7 @@ export const editRole = async (
|
|||
}
|
||||
|
||||
export const deleteRole = async (url: string, id: string): Promise<string | undefined> => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: "deleterole" as ManagementMethod,
|
||||
params: [id],
|
||||
})
|
||||
const {error} = await manageRole(url, "deleterole", [id])
|
||||
|
||||
return error
|
||||
}
|
||||
|
|
@ -161,10 +155,7 @@ export const assignRole = async (
|
|||
pubkey: string,
|
||||
roleId: string,
|
||||
): Promise<string | undefined> => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: "assignrole" as ManagementMethod,
|
||||
params: [pubkey, roleId],
|
||||
})
|
||||
const {error} = await manageRole(url, "assignrole", [pubkey, roleId])
|
||||
|
||||
return error
|
||||
}
|
||||
|
|
@ -174,10 +165,7 @@ export const unassignRole = async (
|
|||
pubkey: string,
|
||||
roleId: string,
|
||||
): Promise<string | undefined> => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: "unassignrole" as ManagementMethod,
|
||||
params: [pubkey, roleId],
|
||||
})
|
||||
const {error} = await manageRole(url, "unassignrole", [pubkey, roleId])
|
||||
|
||||
return error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import type {HTMLAttributes} from "svelte/elements"
|
||||
import cx from "classnames"
|
||||
|
||||
const {
|
||||
children,
|
||||
variant = "neutral",
|
||||
...restProps
|
||||
}: {
|
||||
type Props = HTMLAttributes<HTMLSpanElement> & {
|
||||
children?: Snippet
|
||||
variant?: "neutral" | "primary" | "warning"
|
||||
class?: string
|
||||
[key: string]: any
|
||||
} = $props()
|
||||
}
|
||||
|
||||
const {children, variant = "neutral", ...restProps}: Props = $props()
|
||||
|
||||
const className = $derived(cx("badge", `badge-${variant}`, restProps.class))
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import type {HTMLAttributes} from "svelte/elements"
|
||||
import cx from "classnames"
|
||||
|
||||
type Props = HTMLAttributes<HTMLElement> & {
|
||||
children?: Snippet
|
||||
tag?: string
|
||||
sm?: boolean
|
||||
flat?: boolean
|
||||
interactive?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
children,
|
||||
tag = "div",
|
||||
|
|
@ -9,15 +18,7 @@
|
|||
flat = false,
|
||||
interactive = false,
|
||||
...restProps
|
||||
}: {
|
||||
children?: Snippet
|
||||
tag?: string
|
||||
sm?: boolean
|
||||
flat?: boolean
|
||||
interactive?: boolean
|
||||
class?: string
|
||||
[key: string]: any
|
||||
} = $props()
|
||||
}: Props = $props()
|
||||
|
||||
const className = $derived(
|
||||
cx(
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@
|
|||
title?: Snippet
|
||||
info?: Snippet
|
||||
primary?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const {icon, title, info, primary, ...props}: Props = $props()
|
||||
const {icon, title, info, primary}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="card-button {primary ? 'card-button-primary' : 'card-button-neutral'}">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
type Props = {
|
||||
input?: import("svelte").Snippet
|
||||
content?: import("svelte").Snippet
|
||||
[key: string]: any
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
value?: number | undefined
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import {anchorDatepicker} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
start?: number | undefined
|
||||
end?: number | undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import type {HTMLInputAttributes} from "svelte/elements"
|
||||
import cx from "classnames"
|
||||
|
||||
type Props = Omit<HTMLInputAttributes, "value"> & {
|
||||
value?: string
|
||||
before?: Snippet
|
||||
after?: Snippet
|
||||
sm?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
value = $bindable(),
|
||||
before,
|
||||
|
|
@ -9,14 +17,7 @@
|
|||
sm = false,
|
||||
class: className = "",
|
||||
...restProps
|
||||
}: {
|
||||
value?: any
|
||||
before?: Snippet
|
||||
after?: Snippet
|
||||
sm?: boolean
|
||||
class?: string
|
||||
[key: string]: any
|
||||
} = $props()
|
||||
}: Props = $props()
|
||||
|
||||
const wrapperClass = $derived(cx("input", "input-group", {"input-sm": sm}, className))
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
<script lang="ts" generics="T">
|
||||
import type {Snippet} from "svelte"
|
||||
|
||||
type Props = {
|
||||
items: any[]
|
||||
child: Snippet<[any]>
|
||||
getKey: (item: any) => PropertyKey
|
||||
items: T[]
|
||||
child: Snippet<[T]>
|
||||
getKey: (item: T) => PropertyKey
|
||||
// Minimum column width, in tailwind spacing units (1 = 0.25rem). The browser
|
||||
// fits as many columns as the container allows, so the layout stays
|
||||
// responsive without media queries.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import cx from "classnames"
|
||||
import type {Snippet} from "svelte"
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
element?: Element
|
||||
children?: Snippet
|
||||
noPad?: boolean
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@
|
|||
|
||||
const back = () => history.back()
|
||||
|
||||
const toggleMenu = () => {
|
||||
menuOpen = !menuOpen
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
menuOpen = false
|
||||
}
|
||||
|
||||
const showInfo = () => {
|
||||
menuOpen = false
|
||||
|
||||
|
|
@ -122,11 +130,11 @@
|
|||
<Button
|
||||
class="button button-neutral button-sm button-square"
|
||||
aria-label="More options"
|
||||
onclick={() => (menuOpen = !menuOpen)}>
|
||||
onclick={toggleMenu}>
|
||||
<Icon size={4} icon={MenuDots} />
|
||||
</Button>
|
||||
{#if menuOpen}
|
||||
<Popover hideOnClick onClose={() => (menuOpen = false)}>
|
||||
<Popover hideOnClick onClose={closeMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu bg-surface absolute right-0 z-popover mt-2 w-48 gap-1 rounded-2xl p-2">
|
||||
|
|
|
|||
Loading…
Reference in a new issue