diff --git a/.env b/.env index f3416918..27598282 100644 --- a/.env +++ b/.env @@ -8,10 +8,9 @@ VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy VITE_PLATFORM_NAME=Flotilla VITE_PLATFORM_LOGO=static/logo.png VITE_PLATFORM_RELAYS= -VITE_PLATFORM_RELAYS_STRICT=false VITE_PLATFORM_ACCENT="#7161FF" VITE_THEME="clay" -VITE_PLATFORM_DESCRIPTION="Flotilla is nostr — for communities." +VITE_PLATFORM_DESCRIPTION="The chat app built for self-hosted communities." VITE_PUSH_SERVER=https://nps.flotilla.social/ VITE_PUSH_BRIDGE=wss://npb.coracle.social/ VITE_BLOCKED_RELAYS=brb.io,relay.nostr.band,nostr.mutinywallet.com,feeds.nostr.band,nostr.zbd.gg,wot.utxo.one,blastr.f7z.xyz,relay.current.fyi diff --git a/src/app/components/Landing.svelte b/src/app/components/Landing.svelte index d979f29a..29ecd529 100644 --- a/src/app/components/Landing.svelte +++ b/src/app/components/Landing.svelte @@ -9,7 +9,7 @@ import CardButton from "@lib/components/CardButton.svelte" import LogIn from "@app/components/LogIn.svelte" import SignUp from "@app/components/SignUp.svelte" - import {PLATFORM_TERMS, PLATFORM_PRIVACY, PLATFORM_NAME} from "@app/env" + import {PLATFORM_TERMS, PLATFORM_PRIVACY, PLATFORM_NAME, PLATFORM_DESCRIPTION} from "@app/env" import {pushModal} from "@app/modal" const logIn = () => pushModal(LogIn) @@ -21,7 +21,7 @@

Welcome to {PLATFORM_NAME}!

-

The chat app built for self-hosted communities.

+

{PLATFORM_DESCRIPTION}

diff --git a/src/app/components/SignUp.svelte b/src/app/components/SignUp.svelte index 4fca9bae..86037605 100644 --- a/src/app/components/SignUp.svelte +++ b/src/app/components/SignUp.svelte @@ -2,7 +2,6 @@ import cx from "classnames" import type {ClientOptions} from "@pomade/core" import type {Profile} from "@welshman/util" - import {first} from "@welshman/lib" import {makeProfile, makeSecret, RELAYS, MESSAGING_RELAYS, makeEvent} from "@welshman/util" import {loginWithNip01, publishThunk, waitForThunkCompletion} from "@welshman/app" import Key from "@assets/icons/key-minimalistic.svg?dataurl" @@ -48,7 +47,7 @@ await attemptRelayAccess(url, claim) return url - }) + }), ) // Add default outbox/inbox/messaging relays, profile, spaces diff --git a/src/app/components/SpaceTrustRelay.svelte b/src/app/components/SpaceTrustRelay.svelte index 08713c7e..4bb29c34 100644 --- a/src/app/components/SpaceTrustRelay.svelte +++ b/src/app/components/SpaceTrustRelay.svelte @@ -65,9 +65,8 @@

- This space has opted not to publish digital signatures, which means that they have the ability to forge messages from other users.

diff --git a/src/app/env.ts b/src/app/env.ts index d0009779..42e0cac1 100644 --- a/src/app/env.ts +++ b/src/app/env.ts @@ -52,7 +52,7 @@ export const POMADE_SIGNERS = fromCsv(import.meta.env.VITE_POMADE_SIGNERS) export const DEFAULT_BLOSSOM_SERVERS = fromCsv(import.meta.env.VITE_DEFAULT_BLOSSOM_SERVERS) export const DEFAULT_SPACES = fromCsv(import.meta.env.VITE_DEFAULT_SPACES).map(item => { - const [url, claim = ""] = item.split('|') + const [url, claim = ""] = item.split("|") return [normalizeRelayUrl(url), claim] }) diff --git a/src/app/members.ts b/src/app/members.ts index e7af2405..d5e9b0c3 100644 --- a/src/app/members.ts +++ b/src/app/members.ts @@ -32,20 +32,60 @@ export const deriveSpaceMembers = (url: string) => export const RELAY_ROLE = 33534 +// An hsl color tuple. Any component may be an empty string, in which case the +// client supplies its own default. Usually only `hue` is set per the spec. +export type SpaceRoleColor = { + hue: string // 0 to 360 + saturation: string // 0 to 1 + lightness: string // 0 to 1 +} + export type SpaceRole = { id: string label: string description: string - color: number + color: SpaceRoleColor order: number } -// hue is 0-255; map to 0-360deg. Saturation/lightness chosen to read on both themes. -export const roleColorHue = (color: number) => (((color % 256) + 256) % 256) * (360 / 256) +// Defaults filled in for empty color components, chosen to read on both themes. +const DEFAULT_SATURATION = 0.7 +const DEFAULT_LIGHTNESS = 0.5 -export const roleColor = (color: number) => `hsl(${roleColorHue(color)}, 70%, 50%)` +const roleColorValue = (value: string, fallback: number) => { + const parsed = parseFloat(value) -export const roleColorSoft = (color: number) => `hsl(${roleColorHue(color)}, 70%, 90%)` + return isNaN(parsed) ? fallback : parsed +} + +// Parse the ["color", hue, saturation, lightness] tag, preserving empty values. +export const parseRoleColor = (tags: string[][]): SpaceRoleColor => { + const tag = first(getTags("color", tags)) ?? [] + + return { + hue: tag[1] ?? "", + saturation: tag[2] ?? "", + lightness: tag[3] ?? "", + } +} + +// Build an hsl() string from a role color, using the role's values or our defaults. +export const roleColor = (color: SpaceRoleColor) => { + const h = roleColorValue(color.hue, 0) + const s = roleColorValue(color.saturation, DEFAULT_SATURATION) + const l = roleColorValue(color.lightness, DEFAULT_LIGHTNESS) + + return `hsl(${h}, ${s * 100}%, ${l * 100}%)` +} + +// A translucent tint of the role color for use as a background fill. +export const roleColorSoft = (color: SpaceRoleColor) => { + const h = roleColorValue(color.hue, 0) + const s = roleColorValue(color.saturation, DEFAULT_SATURATION) + const l = roleColorValue(color.lightness, DEFAULT_LIGHTNESS) + + return `hsl(${h}, ${s * 100}%, ${l * 100}%, 0.15)` +} export const deriveSpaceRoles = (url: string) => derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_ROLE]}]), $events => { @@ -59,7 +99,7 @@ export const deriveSpaceRoles = (url: string) => id, label: getTagValue("label", event.tags) ?? "", description: getTagValue("description", event.tags) ?? "", - color: parseInt(getTagValue("color", event.tags) ?? "0", 10) || 0, + color: parseRoleColor(event.tags), order: parseInt(getTagValue("order", event.tags) ?? "0", 10) || 0, }) } @@ -87,17 +127,21 @@ export const deriveSpaceMemberRoles = (url: string) => return memberRoles }) +// Flatten a color tuple into management params, mirroring the color tag's value order. +const roleColorParams = (color: SpaceRoleColor) => + [color.hue, color.saturation, color.lightness] as unknown as string + export const createRole = async ( url: string, id: string, label: string, description: string, - color: number, + color: SpaceRoleColor, order: number, ): Promise => { const {error} = await manageRelay(url, { method: "createrole" as ManagementMethod, - params: [id, label, description, color.toString(), order.toString()], + params: [id, label, description, roleColorParams(color), order.toString()], }) return error @@ -108,12 +152,12 @@ export const editRole = async ( id: string, label: string, description: string, - color: number, + color: SpaceRoleColor, order: number, ): Promise => { const {error} = await manageRelay(url, { method: "editrole" as ManagementMethod, - params: [id, label, description, color.toString(), order.toString()], + params: [id, label, description, roleColorParams(color), order.toString()], }) return error diff --git a/src/app/notifications.ts b/src/app/notifications.ts index f0beca2e..aa311725 100644 --- a/src/app/notifications.ts +++ b/src/app/notifications.ts @@ -18,7 +18,7 @@ import {notificationSettings} from "@app/settings" import {chatsById} from "@app/chats" import {userGroupList, getSpaceUrlsFromGroupList} from "@app/groups" import {hasNip29} from "@app/relays" -import {dufflepud, DUFFLEPUD_URL} from "@app/env" +import {dufflepud, DUFFLEPUD_URL, PLATFORM_RELAYS} from "@app/env" import {kv} from "@app/storage" import {page} from "$app/stores" @@ -224,7 +224,10 @@ export const allNotifications = derived( } } - for (const url of getSpaceUrlsFromGroupList($userGroupList)) { + const urls = + PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : getSpaceUrlsFromGroupList($userGroupList) + + for (const url of urls) { const spacePath = makeSpacePath(url) const events = sortEventsDesc((eventsByIdByUrl.get(url) || new Map()).values()) diff --git a/src/app/sync.ts b/src/app/sync.ts index 3ecbc5f2..ba98965e 100644 --- a/src/app/sync.ts +++ b/src/app/sync.ts @@ -45,7 +45,7 @@ import { hasNegentropy, } from "@welshman/app" import {REACTION_KINDS, CONTENT_KINDS, makeCommentFilter} from "@app/content" -import {INDEXER_RELAYS} from "@app/env" +import {INDEXER_RELAYS, PLATFORM_RELAYS} from "@app/env" import {loadSettings} from "@app/settings" import { loadGroupList, @@ -210,8 +210,10 @@ const syncUserData = () => { const syncGroupList = ($userGroupList: List | undefined) => { if ($userGroupList) { const keys = new Set() + const urls = + PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : getSpaceUrlsFromGroupList($userGroupList) - for (const url of getSpaceUrlsFromGroupList($userGroupList)) { + for (const url of urls) { if (!unsubscribersByKey.has(url)) { unsubscribersByKey.set(url, syncUserSpaceMembership(url)) } @@ -310,7 +312,9 @@ const syncSpaces = () => { const unsubscribersByUrl = new Map() const unsubscribe = store.subscribe(([$userGroupList, $page]) => { - const urls = new Set(getSpaceUrlsFromGroupList($userGroupList)) + const urls = new Set( + PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : getSpaceUrlsFromGroupList($userGroupList), + ) const currentUrl = $page.params.relay ? decodeRelay($page.params.relay) : undefined if (currentUrl) { diff --git a/src/lib/components/StringMultiInput.svelte b/src/lib/components/StringMultiInput.svelte index 3889d689..5e3d7797 100644 --- a/src/lib/components/StringMultiInput.svelte +++ b/src/lib/components/StringMultiInput.svelte @@ -58,7 +58,7 @@

{#each value as item (item)} - {item} diff --git a/src/lib/components/page.css b/src/lib/components/page.css index 0efb4d98..752faba3 100644 --- a/src/lib/components/page.css +++ b/src/lib/components/page.css @@ -3,7 +3,7 @@ @apply ml-sai mb-sai mt-sai mr-sai relative flex min-w-0 grow flex-col md:mb-0 md:ml-0; .page__content { - @apply z-feature mb-14 flex max-h-screen min-h-0 w-full min-w-0 flex-col gap-2 overflow-x-hidden overflow-y-auto sm:gap-4 md:mb-0; + @apply z-feature mb-14 flex h-full max-h-screen min-h-0 w-full min-w-0 flex-col gap-2 overflow-x-hidden overflow-y-auto sm:gap-4 md:mb-0; } .page__content:not(.page__content--no-pad) { diff --git a/src/lib/components/primary-nav.css b/src/lib/components/primary-nav.css index 78dc42ef..e326cd3b 100644 --- a/src/lib/components/primary-nav.css +++ b/src/lib/components/primary-nav.css @@ -4,7 +4,7 @@ } .primary-nav__nav-item { - @apply text-content relative mx-auto my-1 flex h-12 w-12 cursor-pointer items-center justify-center rounded-full transition-all; + @apply text-content relative my-1 flex h-12 w-12 cursor-pointer items-center justify-center rounded-full transition-all; } .primary-nav__nav-item:hover, diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 90e8f865..087c9e21 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,6 @@