Compare commits

...

5 Commits

Author SHA1 Message Date
Jon Staab 65ca8a7fd8 Remove follow graph building 2026-04-08 09:46:56 -07:00
nayan9617 7f1e98dcb2 Fix fallback pull race after abort (#167)
Co-authored-by: nayan9617 <nayanp4925@gmail.com>
Co-committed-by: nayan9617 <nayanp4925@gmail.com>
2026-04-08 16:43:04 +00:00
priyanshu_bharti 4c19ee823b 73-video-thumbnails (#142)
This PR implements video thumbnails for `.mov`, `.webm`, and `.mp4` files in the `ContentLinkBlock` component.

Changes:
- Added the `poster` attribute to the `<video>` tag.
- Set the poster source to `{url}#t=1` to capture a clear preview frame at the 1-second mark.
- Verified locally that thumbnails are now correctly displayed instead of a black/empty box.

Closes #73

Reviewed-on: coracle/flotilla#142
Co-authored-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
Co-committed-by: Priyanshubhartistm <bhartipriyanshustm@gmail.com>
2026-04-08 16:07:11 +00:00
Jon Staab 8e2dd8b278 Upgrade daisyui/tailwind 2026-04-07 15:31:35 -07:00
Jon Staab 8d35b3aad2 Chat tweaks 2026-04-07 10:40:45 -07:00
71 changed files with 872 additions and 816 deletions
+1
View File
@@ -19,5 +19,6 @@ VITE_DEFAULT_SEARCH_RELAYS=relay.ditto.pub,antiprimal.net,relay.vertexlab.io
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,relay.keychat.io,relay.ditto.pub
VITE_SIGNER_RELAYS=relay.nsec.app,ephemeral.snowflare.cc,bucket.coracle.social
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
VITE_THUMBNAIL_URL=
VITE_GLITCHTIP_API_KEY=
GLITCHTIP_AUTH_TOKEN=
+1
View File
@@ -28,6 +28,7 @@ node_modules/
.pnpm-store/
build/
.svelte-kit/
.next/
# Rust/Tauri
*target/
+4 -3
View File
@@ -22,6 +22,7 @@
"@eslint/js": "^9.39.2",
"@sveltejs/kit": "^2.50.1",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
"@tailwindcss/postcss": "^4.2.2",
"@tauri-apps/cli": "^2.9.6",
"@types/eslint": "^9.6.1",
"autoprefixer": "^10.4.23",
@@ -35,7 +36,7 @@
"prettier-plugin-svelte": "^3.4.1",
"svelte": "^5.48.0",
"svelte-check": "^4.3.5",
"tailwindcss": "^3.4.19",
"tailwindcss": "^4.2.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.53.1",
"vite": "^5.4.21"
@@ -77,7 +78,7 @@
"@welshman/store": "^0.8.12",
"@welshman/util": "^0.8.12",
"compressorjs-next": "^1.1.2",
"daisyui": "^4.12.24",
"daisyui": "^5.5.19",
"date-picker-svelte": "^2.17.0",
"dotenv": "^16.6.1",
"emoji-picker-element": "^1.28.1",
@@ -87,7 +88,7 @@
"livekit-client": "^2.17.2",
"nostr-signer-capacitor-plugin": "github:coracle-social/nostr-signer-capacitor-plugin#main",
"nostr-tools": "^2.19.4",
"prettier-plugin-tailwindcss": "^0.6.14",
"prettier-plugin-tailwindcss": "^0.7.2",
"qr-scanner": "^1.4.2",
"qrcode": "^1.5.4",
"throttle-debounce": "^5.0.2",
+388 -369
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
}
+246 -248
View File
@@ -1,149 +1,245 @@
@import "@welshman/editor/index.css";
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;
@config "../tailwind.config.js";
/* Fonts */
@font-face {
font-family: "Satoshis";
font-style: normal;
font-weight: 400;
src:
local(""),
url("/fonts/Satoshi Symbol.ttf") format("truetype");
@utility pt-sai {
padding-top: var(--sait);
}
@font-face {
font-family: "Lato";
font-style: normal;
font-weight: 400;
src:
local(""),
url("/fonts/Lato-Regular.ttf") format("truetype");
@utility pr-sai {
padding-right: var(--sair);
}
@font-face {
font-family: "Lato";
font-style: bold;
font-weight: 600;
src:
local(""),
url("/fonts/Lato-Bold.ttf") format("truetype");
@utility pb-sai {
padding-bottom: var(--saib);
}
@font-face {
font-family: "Lato";
font-style: italic;
font-weight: 400;
src:
local(""),
url("/fonts/Italic.ttf") format("truetype");
@utility pl-sai {
padding-left: var(--sail);
}
/* root */
:root {
font-family: Lato;
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
--sair: var(--safe-area-inset-right, env(safe-area-inset-right));
@utility px-sai {
@apply pl-sai pr-sai;
}
[data-theme] {
@apply bg-base-300;
--base-100: oklch(var(--b1));
--base-200: oklch(var(--b2));
--base-300: oklch(var(--b3));
--base-content: oklch(var(--bc));
--primary: oklch(var(--p));
--primary-content: oklch(var(--pc));
--secondary: oklch(var(--s));
--secondary-content: oklch(var(--sc));
--neutral: oklch(var(--n));
--neutral-content: oklch(var(--nc));
@utility py-sai {
@apply pt-sai pb-sai;
}
.mobile [data-tip]::before {
display: none !important;
@utility p-sai {
@apply py-sai px-sai;
}
/* safe area insets */
@utility mt-sai {
margin-top: var(--sait);
}
@layer components {
.pt-sai {
padding-top: var(--sait);
@utility mr-sai {
margin-right: var(--sair);
}
@utility mb-sai {
margin-bottom: var(--saib);
}
@utility ml-sai {
margin-left: var(--sail);
}
@utility mx-sai {
@apply ml-sai mr-sai;
}
@utility my-sai {
@apply mt-sai mb-sai;
}
@utility m-sai {
@apply my-sai mx-sai;
}
@utility top-sai {
top: var(--sait);
}
@utility right-sai {
right: var(--sair);
}
@utility bottom-sai {
bottom: var(--saib);
}
@utility left-sai {
left: var(--sail);
}
@utility card2 {
@apply rounded-box text-base-content p-4 sm:p-6;
}
@utility column {
@apply flex flex-col;
}
@utility center {
@apply flex items-center justify-center;
}
@utility row-2 {
@apply flex items-center gap-2;
}
@utility row-3 {
@apply flex items-center gap-3;
}
@utility row-4 {
@apply flex items-center gap-4;
}
@utility col-2 {
@apply flex flex-col gap-2;
}
@utility col-3 {
@apply flex flex-col gap-3;
}
@utility col-4 {
@apply flex flex-col gap-4;
}
@utility col-8 {
@apply flex flex-col gap-8;
}
@utility ellipsize {
@apply overflow-hidden text-ellipsis;
}
@utility content-padding-x {
@apply px-4 sm:px-8 md:px-12;
}
@utility content-padding-t {
@apply pt-4 sm:pt-8 md:pt-12;
}
@utility content-padding-b {
@apply pb-4 sm:pb-8 md:pb-12;
}
@utility content-padding-y {
@apply pt-4 pb-4 sm:pt-8 sm:pb-8 md:pt-12 md:pb-12;
}
@utility content-sizing {
@apply m-auto w-full max-w-3xl;
}
@utility content {
@apply m-auto w-full max-w-3xl px-4 pt-4 pb-4 sm:px-8 sm:pt-8 sm:pb-8 md:px-12 md:pt-12 md:pb-12;
}
@utility heading {
@apply text-center text-2xl;
}
@utility subheading {
@apply text-center text-xl;
}
@utility superheading {
@apply text-center text-4xl;
}
@utility link {
@apply text-primary cursor-pointer underline;
}
/* content visibility */
@utility cv {
content-visibility: auto;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer utilities {
/* Fonts */
@font-face {
font-family: "Satoshis";
font-style: normal;
font-weight: 400;
src:
local(""),
url("/fonts/Satoshi Symbol.ttf") format("truetype");
}
.pr-sai {
padding-right: var(--sair);
@font-face {
font-family: "Lato";
font-style: normal;
font-weight: 400;
src:
local(""),
url("/fonts/Lato-Regular.ttf") format("truetype");
}
.pb-sai {
padding-bottom: var(--saib);
@font-face {
font-family: "Lato";
font-style: bold;
font-weight: 600;
src:
local(""),
url("/fonts/Lato-Bold.ttf") format("truetype");
}
.pl-sai {
padding-left: var(--sail);
@font-face {
font-family: "Lato";
font-style: italic;
font-weight: 400;
src:
local(""),
url("/fonts/Italic.ttf") format("truetype");
}
.px-sai {
@apply pl-sai pr-sai;
/* root */
:root {
font-family: Lato;
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
--sair: var(--safe-area-inset-right, env(safe-area-inset-right));
}
.py-sai {
@apply pt-sai pb-sai;
[data-theme] {
@apply bg-base-300;
}
.p-sai {
@apply py-sai px-sai;
.mobile [data-tip]::before {
display: none !important;
}
.mt-sai {
margin-top: var(--sait);
}
.mr-sai {
margin-right: var(--sair);
}
.mb-sai {
margin-bottom: var(--saib);
}
.ml-sai {
margin-left: var(--sail);
}
.mx-sai {
@apply ml-sai mr-sai;
}
.my-sai {
@apply mt-sai mb-sai;
}
.m-sai {
@apply my-sai mx-sai;
}
.top-sai {
top: var(--sait);
}
.right-sai {
right: var(--sair);
}
.bottom-sai {
bottom: var(--saib);
}
.left-sai {
left: var(--sail);
}
/* safe area insets */
}
/* utilities */
@@ -165,110 +261,18 @@
@apply bg-base-300 text-base-content transition-colors;
}
.card2 {
@apply rounded-box p-4 text-base-content sm:p-6;
}
.card2.card2-sm {
@apply p-2 text-base-content sm:p-4;
}
.column {
@apply flex flex-col;
}
.center {
@apply flex items-center justify-center;
}
.row-2 {
@apply flex items-center gap-2;
}
.row-3 {
@apply flex items-center gap-3;
}
.row-4 {
@apply flex items-center gap-4;
}
.col-2 {
@apply flex flex-col gap-2;
}
.col-3 {
@apply flex flex-col gap-3;
}
.col-4 {
@apply flex flex-col gap-4;
}
.col-8 {
@apply flex flex-col gap-8;
}
.badge {
@apply justify-start overflow-hidden text-ellipsis whitespace-nowrap;
}
.ellipsize {
@apply overflow-hidden text-ellipsis;
@apply text-base-content p-2 sm:p-4;
}
[data-tip]::before {
@apply ellipsize;
}
.content-padding-x {
@apply px-4 sm:px-8 md:px-12;
}
.content-padding-t {
@apply pt-4 sm:pt-8 md:pt-12;
}
.content-padding-b {
@apply pb-4 sm:pb-8 md:pb-12;
}
.content-padding-y {
@apply content-padding-t content-padding-b;
}
.content-sizing {
@apply m-auto w-full max-w-3xl;
}
.content {
@apply content-sizing content-padding-x content-padding-y;
}
.heading {
@apply text-center text-2xl;
}
.subheading {
@apply text-center text-xl;
}
.superheading {
@apply text-center text-4xl;
}
.link {
@apply cursor-pointer text-primary underline;
@apply overflow-hidden text-ellipsis;
}
.input input::placeholder {
opacity: 0.5;
}
.shadow-top-xl {
@apply shadow-[0_20px_25px_-5px_rgb(0,0,0,0.1)_0_8px_10px_-6px_rgb(0,0,0,0.1)];
}
/* tiptap */
.input-editor,
@@ -278,21 +282,21 @@
}
.tiptap {
--tiptap-object-bg: var(--neutral);
--tiptap-object-fg: var(--neutral-content);
--tiptap-active-bg: var(--primary);
--tiptap-active-fg: var(--primary-content);
--tiptap-object-bg: var(--color-neutral);
--tiptap-object-fg: var(--color-neutral-content);
--tiptap-active-bg: var(--color-primary);
--tiptap-active-fg: var(--color-primary-content);
}
.tiptap-suggestions {
--tiptap-object-bg: var(--base-100);
--tiptap-object-fg: var(--base-content);
--tiptap-active-bg: var(--base-300);
--tiptap-active-fg: var(--base-content);
--tiptap-object-bg: var(--color-base-100);
--tiptap-object-fg: var(--color-base-content);
--tiptap-active-bg: var(--color-base-300);
--tiptap-active-fg: var(--color-base-content);
}
.tiptap-suggestions__item {
@apply border-l-2 border-solid border-base-100;
@apply border-base-100 border-l-2 border-solid;
}
.tiptap-suggestions__selected {
@@ -312,13 +316,13 @@
}
.note-editor .tiptap {
--tiptap-object-bg: var(--base-200);
@apply input input-bordered h-auto min-h-32 rounded-box p-[.65rem] pb-6;
--tiptap-object-bg: var(--color-base-200);
@apply input rounded-box h-auto min-h-32 p-[.65rem] pb-6;
}
.input-editor .tiptap {
--tiptap-object-bg: var(--base-200);
@apply input input-bordered h-auto p-[.65rem];
--tiptap-object-bg: var(--color-base-200);
@apply input h-auto p-[.65rem];
}
/* link-content, based on tiptap */
@@ -330,8 +334,8 @@
white-space: nowrap;
border-radius: 3px;
padding: 0 0.25rem;
background-color: var(--base-100);
color: var(--base-content);
background-color: var(--color-base-100);
color: var(--color-base-content);
}
/* content rendered by welshman/content */
@@ -347,25 +351,25 @@
/* date input */
.picker {
--date-picker-foreground: var(--base-content);
--date-picker-background: var(--base-300);
--date-picker-highlight-border: var(--primary);
--date-picker-selected-color: var(--primary-content);
--date-picker-selected-background: var(--primary);
--date-picker-foreground: var(--color-base-content);
--date-picker-background: var(--color-base-300);
--date-picker-highlight-border: var(--color-primary);
--date-picker-selected-color: var(--color-primary-content);
--date-picker-selected-background: var(--color-primary);
}
.date-time-field {
@apply input input-bordered rounded-lg px-0;
@apply input rounded-lg px-0;
}
.date-time-field input {
@apply !h-full !w-full !rounded-lg !border-none !bg-inherit !px-4 !text-inherit;
@apply h-full! w-full! rounded-lg! border-none! bg-inherit! px-4! text-inherit!;
}
/* tippy popover */
.tippy-target {
@apply pointer-events-none fixed inset-0 z-tooltip;
@apply z-tooltip pointer-events-none fixed inset-0;
}
.tippy-target > * {
@@ -379,15 +383,15 @@
/* emoji picker */
emoji-picker {
--background: var(--base-100);
--border-color: var(--base-100);
--background: var(--color-base-100);
--border-color: var(--color-base-100);
--border-radius: var(--rounded-box);
--button-active-background: var(--base-content);
--button-hover-background: var(--base-content);
--indicator-color: var(--base-content);
--input-border-color: var(--base-100);
--input-font-color: var(--base-content);
--outline-color: var(--base-100);
--button-active-background: var(--color-base-content);
--button-hover-background: var(--color-base-content);
--indicator-color: var(--color-base-content);
--input-border-color: var(--color-base-100);
--input-font-color: var(--color-base-content);
--outline-color: var(--color-base-100);
}
/* progress */
@@ -411,7 +415,7 @@ body.keyboard-open .hide-on-keyboard {
/* chat view */
.chat__compose {
@apply relative z-compose mb-14 flex-grow md:mb-0;
@apply z-compose relative mb-14 grow md:mb-0;
}
.chat__compose .chat__compose-inner {
@@ -419,11 +423,5 @@ body.keyboard-open .hide-on-keyboard {
}
.chat__scroll-down {
@apply pb-sai fixed bottom-28 right-4 z-feature md:bottom-16;
}
/* content visibility */
.cv {
content-visibility: auto;
@apply pb-sai z-feature fixed right-4 bottom-28 md:bottom-16;
}
@@ -38,7 +38,7 @@
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
</script>
<div class="flex flex-grow flex-wrap justify-end gap-2">
<div class="flex grow flex-wrap justify-end gap-2">
{#if h && showRoom}
<Link href={makeSpacePath(url, h)} class="btn btn-neutral btn-xs rounded-full">
Posted in #<RoomName {h} {url} />
+1 -1
View File
@@ -155,7 +155,7 @@
{#snippet input()}
<div
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="input-editor flex-grow overflow-hidden">
<div class="input-editor grow overflow-hidden">
<EditorContent {editor} />
</div>
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
@@ -19,7 +19,7 @@
const end = $derived(parseInt(meta.end))
</script>
<div class="flex flex-grow flex-wrap justify-between gap-2">
<div class="flex grow flex-wrap justify-between gap-2">
<p class="text-xl">{meta.title || meta.name}</p>
{#if !isNaN(start) && !isNaN(end)}
{@const startDateDisplay = formatTimestampAsDate(start)}
+1 -1
View File
@@ -23,7 +23,7 @@
{#if meta.location}
<span class="flex items-start gap-1">
<Icon icon={MapPoint} class="mt-[2px]" size={4} />
<span class="break-words">{meta.location}</span>
<span class="wrap-break-word">{meta.location}</span>
</span>
{/if}
</div>
+1 -1
View File
@@ -43,7 +43,7 @@
const uploading = writable(false)
const editorClass = $derived(
cx("chat-editor flex-grow overflow-hidden", {
cx("chat-editor grow overflow-hidden", {
"pointer-events-none opacity-50": disabled,
}),
)
+1 -1
View File
@@ -35,7 +35,7 @@
<Button class="flex flex-col justify-start gap-1 w-full" onclick={openChat}>
<div
class="cursor-pointer border-t border-solid border-base-100 px-6 py-2 transition-colors hover:bg-base-100 {props.class}"
class="cursor-pointer border-t border-solid border-base-100 px-3 py-2 transition-colors hover:bg-base-100 {props.class}"
class:bg-base-100={active}>
<div class="flex flex-col justify-start gap-1">
<div class="flex items-center justify-between gap-2">
+1 -1
View File
@@ -42,7 +42,7 @@
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
</script>
<div class="flex flex-grow flex-wrap justify-end gap-2">
<div class="flex grow flex-wrap justify-end gap-2">
{#if h && showRoom}
<Link href={makeSpacePath(url, h)} class="btn btn-neutral btn-xs rounded-full">
Posted in #<RoomName {h} {url} />
+1 -1
View File
@@ -172,7 +172,7 @@
<p>Description*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<div class="note-editor grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
+1 -1
View File
@@ -28,7 +28,7 @@
</script>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="flex flex-grow flex-wrap justify-end gap-2">
<div class="flex grow flex-wrap justify-end gap-2">
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
<ThunkStatusOrDeleted {event} />
{#if showActivity}
+1 -1
View File
@@ -150,7 +150,7 @@
</div>
{:else}
<div
class="overflow-hidden text-ellipsis break-words"
class="overflow-hidden text-ellipsis wrap-break-word"
style={expandBlock ? "mask-image: linear-gradient(0deg, transparent 0px, black 100px)" : ""}>
{#each shortContent as parsed, i}
{#if isNewline(parsed) && !isBlock(i - 1)}
+22 -2
View File
@@ -1,12 +1,19 @@
<script lang="ts">
import {call, ellipsize, displayUrl, postJson} from "@welshman/lib"
import {isRelayUrl, getTagValue} from "@welshman/util"
import {Capacitor} from "@capacitor/core"
import {preventDefault, stopPropagation} from "@lib/html"
import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
import {pushModal} from "@app/util/modal"
import {dufflepud, PLATFORM_URL, IMAGE_CONTENT_TYPES, VIDEO_CONTENT_TYPES} from "@app/core/state"
import {
dufflepud,
PLATFORM_URL,
IMAGE_CONTENT_TYPES,
VIDEO_CONTENT_TYPES,
THUMBNAIL_URL,
} from "@app/core/state"
import {makeSpacePath} from "@app/util/routes"
const {value, event} = $props()
@@ -22,6 +29,14 @@
return [url, true]
})
const getVideoPoster = (videoUrl: string): string | undefined => {
if (Capacitor.getPlatform() === "android" && THUMBNAIL_URL) {
return `${THUMBNAIL_URL}/thumbnail?url=${encodeURIComponent(videoUrl)}`
}
return undefined
}
const loadPreview = async () => {
const json = await postJson(dufflepud("link/preview"), {url})
@@ -42,7 +57,12 @@
<Link {external} {href} class="my-2 block">
<div class="overflow-hidden rounded-box">
{#if url.match(/\.(mov|webm|mp4)$/) || VIDEO_CONTENT_TYPES.includes(fileType)}
<video controls src={url} class="max-h-96 rounded-box object-contain object-center">
<video
controls
src={url}
poster={getVideoPoster(url)}
preload="metadata"
class="max-h-96 rounded-box object-contain object-center">
<track kind="captions" />
</video>
{:else if url.match(/\.(jpe?g|png|gif|webp)$/) || IMAGE_CONTENT_TYPES.includes(fileType)}
+1 -1
View File
@@ -101,7 +101,7 @@
</p>
</div>
{:else}
<div class="overflow-hidden text-ellipsis break-words">
<div class="overflow-hidden text-ellipsis wrap-break-word">
{#each shortContent as parsed, i}
{#if isNewline(parsed)}
<ContentNewline value={parsed.value} />
+1 -1
View File
@@ -45,7 +45,7 @@
{#if $quote.kind === MESSAGE}
<div
class="border-l-2 border-solid border-l-primary py-1 pl-2 opacity-90"
style="background-color: color-mix(in srgb, var(--primary) 10%, var(--base-300) 90%);">
style="background-color: color-mix(in srgb, var(--color-primary) 10%, var(--color-base-300) 90%);">
<NoteContentMinimal trimParent {url} event={$quote} />
</div>
{:else}
+2 -2
View File
@@ -101,7 +101,7 @@
{/if}
<div class="relative">
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
<p class="absolute right-2 top-2 flex grow items-center justify-between">
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
<Icon icon={Copy} /> Copy
</Button>
@@ -109,6 +109,6 @@
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary flex-grow" onclick={() => history.back()}>Got it</Button>
<Button class="btn btn-primary grow" onclick={() => history.back()}>Got it</Button>
</ModalFooter>
</Modal>
+1 -1
View File
@@ -87,7 +87,7 @@
class="left-content bottom-sai right-sai ml-2 pl-2 fixed z-feature">
<div class="card2 mx-2 my-2 bg-alt shadow-md">
<div class="relative">
<div class="note-editor flex-grow overflow-hidden">
<div class="note-editor grow overflow-hidden">
<EditorContent {autofocus} {editor} />
</div>
<Button
+1 -1
View File
@@ -30,7 +30,7 @@
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
</script>
<div class="flex flex-grow flex-wrap justify-end gap-2">
<div class="flex grow flex-wrap justify-end gap-2">
{#if h && showRoom}
<Link href={makeSpacePath(url, h)} class="btn btn-neutral btn-xs rounded-full">
Posted in #<RoomName {h} {url} />
+2 -2
View File
@@ -146,7 +146,7 @@
<p>Details*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<div class="note-editor grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
@@ -168,7 +168,7 @@
Goal Amount (sats)*
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<div class="flex grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-28" />
+1 -1
View File
@@ -23,7 +23,7 @@
<ModalTitle>Unable to Zap</ModalTitle>
</ModalHeader>
<p>
Zapping <ProfileLink {pubkey} class="!text-primary" /> isn't possible right now because
Zapping <ProfileLink {pubkey} class="text-primary!" /> isn't possible right now because
{#if $zapper}
their zap receiver isn't correctly set up.
{:else}
@@ -97,10 +97,10 @@
tabindex="-1"
onmousedown={stopPropagation(onClear)}
ontouchstart={stopPropagation(onClear)}>
<Icon icon={CloseCircle} class="scale-150 !bg-base-300" />
<Icon icon={CloseCircle} class="scale-150 bg-base-300!" />
</span>
{:else}
<Icon icon={AddCircle} class="scale-150 !bg-base-300" />
<Icon icon={AddCircle} class="scale-150 bg-base-300!" />
{/if}
</div>
{#if !url}
@@ -9,10 +9,10 @@
<div class="flex items-start gap-4">
<CalendarEventDate event={props.event} />
<div class="flex flex-grow flex-col">
<div class="flex grow flex-col">
<CalendarEventHeader event={props.event} />
<div class="flex py-2 opacity-50">
<div class="h-px flex-grow bg-base-content opacity-25"></div>
<div class="h-px grow bg-base-content opacity-25"></div>
</div>
<Content {...props} />
</div>
@@ -17,7 +17,7 @@
</script>
<div class="flex flex-col">
<div class="flex flex-grow flex-wrap justify-between gap-2">
<div class="flex grow flex-wrap justify-between gap-2">
<p class="text-sm">{meta.title || meta.name}</p>
{#if !isNaN(start) && !isNaN(end)}
{@const startDateDisplay = formatTimestampAsDate(start)}
+1 -1
View File
@@ -43,7 +43,7 @@
<div class="flex flex-col gap-2 card2 card2-sm bg-alt">
<div class="flex items-center justify-between gap-2">
<label class="flex min-w-0 flex-grow items-center gap-2">
<label class="flex min-w-0 grow items-center gap-2">
{#if !closed}
{#if pollType === "singlechoice"}
<input
+2 -3
View File
@@ -32,7 +32,7 @@
</script>
<div
class="ml-sai mt-sai mb-sai relative z-popover isolate hidden w-14 flex-shrink-0 bg-base-200 pt-2 md:block">
class="ml-sai mt-sai mb-sai relative z-popover isolate hidden w-14 shrink-0 bg-base-200 pt-2 md:block">
<div class="flex h-full flex-col" class:justify-between={PLATFORM_RELAYS.length === 0}>
<PrimaryNavSpaces />
{#if PLATFORM_RELAYS.length > 0}
@@ -62,8 +62,7 @@
{@render children?.()}
<!-- a little extra something for ios -->
<div
class="hide-on-keyboard fixed bottom-0 left-0 right-0 z-nav h-[var(--saib)] bg-base-100 md:hidden">
<div class="hide-on-keyboard fixed bottom-0 left-0 right-0 z-nav h-(--saib) bg-base-100 md:hidden">
</div>
<div
class="hide-on-keyboard border-top bottom-sai fixed left-0 right-0 z-nav h-14 border border-base-200 bg-base-100 md:hidden">
+2 -2
View File
@@ -25,10 +25,10 @@
<div class="flex flex-col gap-2">
{#each spaceUrls as url (url)}
<div class="card2 bg-alt flex flex-row items-center gap-2">
<div class="flex-shrink-0">
<div class="shrink-0">
<RelayIcon {url} size={12} />
</div>
<div class="flex flex-grow flex-col">
<div class="flex grow flex-col">
<RelayName {url} />
<div class="text-sm opacity-75">
{url}
+1 -1
View File
@@ -121,6 +121,6 @@
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary flex-grow" onclick={back}>Done</Button>
<Button class="btn btn-primary grow" onclick={back}>Done</Button>
</ModalFooter>
</Modal>
+2 -2
View File
@@ -26,8 +26,8 @@
type="button"
class="btn font-normal flex h-[unset] w-full flex-nowrap py-4 text-left items-start justify-between"
{onclick}>
<div class="flex flex-grow flex-row items-start gap-4">
<div class="flex h-7 w-7 flex-shrink-0 items-center justify-center">
<div class="flex grow flex-row items-start gap-4">
<div class="flex h-7 w-7 shrink-0 items-center justify-center">
<Icon {icon} />
</div>
<div class="flex flex-col gap-1">
+1 -1
View File
@@ -23,7 +23,7 @@
<div class="relative">
<div class="avatar relative">
<div
class="center !flex h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
class="center flex! h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
<RelayIcon {url} />
</div>
</div>
+1 -1
View File
@@ -132,7 +132,7 @@
</Button>
</Tippy>
</div>
<div class="chat-editor flex-grow overflow-hidden">
<div class="chat-editor grow overflow-hidden">
<EditorContent {autofocus} {editor} />
</div>
<Button
+1 -1
View File
@@ -131,7 +131,7 @@
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-between gap-4">
<div class="flex grow items-center justify-between gap-4">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
+1 -1
View File
@@ -94,7 +94,7 @@
{:else}
<div class="w-8 shrink-0"></div>
{/if}
<div class="min-w-0 flex-grow pr-1">
<div class="min-w-0 grow pr-1">
{#if showPubkey}
<div class="flex items-center gap-2">
<Button onclick={openProfile} class="text-sm font-bold" style="color: {colorValue}">
+1 -1
View File
@@ -11,7 +11,7 @@
const {url, h, ...props}: Props = $props()
</script>
<div class="flex flex-grow items-center justify-between gap-4 {props.class}">
<div class="flex grow items-center justify-between gap-4 {props.class}">
<div class="flex items-center gap-3">
<RoomImage {url} {h} />
<div class="min-w-0 overflow-hidden text-ellipsis">
+1 -1
View File
@@ -27,7 +27,7 @@
<Button onclick={back} class="place-self-start pr-3 md:hidden">
<Icon icon={ArrowLeft} size={7} />
</Button>
<div class="ellipsize whitespace-nowrap flex flex-grow items-center justify-between gap-4">
<div class="ellipsize whitespace-nowrap flex grow items-center justify-between gap-4">
<div class="flex flex-col">
<div class="flex gap-2 items-center">
{@render title?.()}
+1 -1
View File
@@ -42,7 +42,7 @@
<div class="relative">
<div class="avatar relative">
<div
class="center !flex h-16 w-16 min-w-16 rounded-full border-2 border-solid border-base-300 bg-base-300">
class="center flex! h-16 w-16 min-w-16 rounded-full border-2 border-solid border-base-300 bg-base-300">
<RelayIcon {url} size={10} />
</div>
</div>
+1 -1
View File
@@ -134,7 +134,7 @@
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex items-center gap-4 justify-between flex-grow">
<div class="flex items-center gap-4 justify-between grow">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
+1 -1
View File
@@ -100,6 +100,6 @@
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary flex-grow" onclick={back}>Done</Button>
<Button class="btn btn-primary grow" onclick={back}>Done</Button>
</ModalFooter>
</Modal>
+6 -6
View File
@@ -140,7 +140,7 @@
<div bind:this={element} class="flex min-h-0 flex-1 flex-col">
<SecondaryNavSection class="min-h-0 flex-1 flex flex-col overflow-hidden pb-0">
<div class="flex-shrink-0">
<div class="shrink-0">
<Button
class="relative flex w-full flex-col rounded-xl p-3 transition-all hover:bg-base-100"
onclick={openMenu}>
@@ -270,14 +270,14 @@
{/if}
{#if hasNip29($relay)}
{#if $userRooms.length > 0}
<div class="h-2 flex-shrink-0"></div>
<div class="h-2 shrink-0"></div>
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
{/if}
{#each $userRooms as h (h)}
<SpaceMenuRoomItem {url} {h} />
{/each}
{#if $otherRooms.length > 0}
<div class="h-2 flex-shrink-0"></div>
<div class="h-2 shrink-0"></div>
<SecondaryNavHeader>
{#if $userRooms.length > 0}
Other Rooms
@@ -296,7 +296,7 @@
<SpaceMenuRoomItem {url} {h} />
{/each}
{#if $otherVoiceRooms.length > 0}
<div class="h-2 flex-shrink-0"></div>
<div class="h-2 shrink-0"></div>
<SecondaryNavHeader>Voice Rooms</SecondaryNavHeader>
{#each $otherVoiceRooms as h (h)}
<SpaceMenuRoomItem {url} {h} />
@@ -309,11 +309,11 @@
</SecondaryNavItem>
{/if}
{/if}
<div class="h-5 flex-shrink-0"></div>
<div class="h-5 shrink-0"></div>
</div>
</SecondaryNavSection>
<div
class="flex flex-shrink-0 flex-col gap-2 p-2 pt-0 -mt-4 pb-[calc(var(--saib)+0.25rem)] md:pb-2 z-nav">
class="flex shrink-0 flex-col gap-2 p-2 pt-0 -mt-4 pb-[calc(var(--saib)+0.25rem)] md:pb-2 z-nav">
<VoiceWidget />
<Button class="btn btn-neutral btn-sm h-10" onclick={showDetail}>
<SocketStatusIndicator {url} />
+1 -1
View File
@@ -30,7 +30,7 @@
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
</script>
<div class="flex flex-grow flex-wrap justify-end gap-2">
<div class="flex grow flex-wrap justify-end gap-2">
{#if h && showRoom}
<Link href={makeSpacePath(url, h)} class="btn btn-neutral btn-xs rounded-full">
Posted in #<RoomName {h} {url} />
+1 -1
View File
@@ -130,7 +130,7 @@
<p>Message*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<div class="note-editor grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
+1 -1
View File
@@ -64,7 +64,7 @@
{replaceState}
{notification}
onclick={handleClick}
class={cx("!items-start", isActive && "!bg-base-100 !text-base-content")}>
class={cx("items-start!", isActive && "bg-base-100! text-base-content!")}>
<div class="flex w-full min-w-0 flex-col gap-2">
<div class="flex gap-2 items-center">
{#if isJoining}
+1 -1
View File
@@ -70,7 +70,7 @@
Amount (satoshis)
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<div class="flex grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input
+1 -1
View File
@@ -80,7 +80,7 @@
Amount (satoshis)
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<div class="flex grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={sats} type="number" class="w-14" placeholder="0" />
+2 -2
View File
@@ -1,7 +1,7 @@
<style>
.wot-background {
fill: transparent;
stroke: var(--base-content);
stroke: var(--color-base-content);
opacity: 30%;
}
@@ -32,7 +32,7 @@
const normalizedScore = $derived(clamp([0, max], $score) / max)
const dashOffset = $derived(100 - 44 * normalizedScore)
const style = $derived(`transform: rotate(${135 - normalizedScore * 180}deg)`)
const stroke = $derived(active ? "var(--primary)" : "var(--base-content)")
const stroke = $derived(active ? "var(--color-primary)" : "var(--color-base-content)")
</script>
<div class="relative h-[14px] w-[14px]">
+5 -5
View File
@@ -118,26 +118,26 @@
<ModalBody>
<ModalHeader>
<ModalTitle>Send a Zap</ModalTitle>
<ModalSubtitle>To <ProfileLink {pubkey} class="!text-primary" /></ModalSubtitle>
<ModalSubtitle>To <ProfileLink {pubkey} class="text-primary!" /></ModalSubtitle>
</ModalHeader>
<FieldInline class="!grid-cols-3">
<FieldInline class="grid-cols-3!">
{#snippet label()}
Emoji Reaction
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-end gap-4">
<div class="flex grow items-center justify-end gap-4">
<EmojiButton {onEmoji} class="btn btn-neutral">
{content}
</EmojiButton>
</div>
{/snippet}
</FieldInline>
<FieldInline class="!grid-cols-3">
<FieldInline class="grid-cols-3!">
{#snippet label()}
Amount
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<div class="flex grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-24" />
+6 -6
View File
@@ -147,7 +147,7 @@
<ModalBody>
<ModalHeader>
<ModalTitle>Send a Zap</ModalTitle>
<ModalSubtitle>To <ProfileLink {pubkey} class="!text-primary" /></ModalSubtitle>
<ModalSubtitle>To <ProfileLink {pubkey} class="text-primary!" /></ModalSubtitle>
</ModalHeader>
{#if invoice}
@@ -158,30 +158,30 @@
</p>
</div>
<label class="input input-bordered flex w-full items-center justify-between gap-2">
<input readonly class="ellipsize flex-grow" value={invoice} />
<input readonly class="ellipsize grow" value={invoice} />
<Button class="flex items-center" onclick={copyInvoice}>
<Icon icon={Copy} />
</Button>
</label>
{:else}
<FieldInline class="!grid-cols-3">
<FieldInline class="grid-cols-3!">
{#snippet label()}
Emoji Reaction
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-end gap-4">
<div class="flex grow items-center justify-end gap-4">
<EmojiButton {onEmoji} class="btn btn-neutral">
{content}
</EmojiButton>
</div>
{/snippet}
</FieldInline>
<FieldInline class="!grid-cols-3">
<FieldInline class="grid-cols-3!">
{#snippet label()}
Amount
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<div class="flex grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-24" />
+2
View File
@@ -210,6 +210,8 @@ export const DEFAULT_PUBKEYS = import.meta.env.VITE_DEFAULT_PUBKEYS
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
export const THUMBNAIL_URL = import.meta.env.VITE_THUMBNAIL_URL
export const NIP46_PERMS =
"nip44_encrypt,nip44_decrypt," +
[
+74 -64
View File
@@ -1,8 +1,8 @@
import {page} from "$app/stores"
import type {Unsubscriber} from "svelte/store"
import {derived, get} from "svelte/store"
import {last, call, ifLet, assoc, chunk, sleep, identity, WEEK, ago} from "@welshman/lib"
import {last, call, ifLet, assoc, chunk, WEEK, ago} from "@welshman/lib"
import {PollResponse} from "nostr-tools/kinds"
import {merged} from "@welshman/store"
import {
getListTags,
getRelayTagValues,
@@ -21,12 +21,11 @@ import {
unionFilters,
getTagValue,
} from "@welshman/util"
import type {Filter, TrustedEvent} from "@welshman/util"
import type {Filter, List, PublishedList, TrustedEvent} from "@welshman/util"
import {request, requestOne, Difference, DifferenceEvent} from "@welshman/net"
import {
pubkey,
loadRelay,
userFollowList,
userRelayList,
userMessagingRelayList,
loadRelayList,
@@ -49,7 +48,6 @@ import {
loadGroupList,
userSpaceUrls,
userGroupList,
bootstrapPubkeys,
decodeRelay,
getSpaceUrlsFromGroupList,
getSpaceRoomsFromGroupList,
@@ -74,6 +72,8 @@ const pullOneWithFallback = async (
signal: AbortSignal,
onEvent?: (event: TrustedEvent) => void,
) => {
if (signal.aborted) return
const cachedEvents = repository.query([filter]).filter(isSignedEvent)
const since = last(cachedEvents.slice(10))?.created_at || 0
@@ -86,6 +86,12 @@ const pullOneWithFallback = async (
const shouldFallback =
!hasNegentropy(url) ||
(await new Promise(resolve => {
if (signal.aborted) {
resolve(false)
return
}
// If teardown wins while the diff is opening, skip the fallback path and let cleanup stay in control.
const diff = new Difference({relay: url, filter, events: cachedEvents, signal})
diff.on(DifferenceEvent.Error, () => {
@@ -111,9 +117,7 @@ export const pullWithFallback = async ({url, signal, filters, onEvent}: SyncOpts
if (signal.aborted) return
for (const filter of filters) {
pullOneWithFallback(url, filter, signal, onEvent)
}
await Promise.all(filters.map(filter => pullOneWithFallback(url, filter, signal, onEvent)))
}
const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
@@ -123,6 +127,8 @@ const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
}
const pullAndListen = (options: SyncOpts) => {
if (options.signal.aborted) return
pullWithFallback(options)
listen(options)
}
@@ -197,7 +203,7 @@ const syncUserRoomMembership = (url: string, h: string) => {
const syncUserData = () => {
const unsubscribersByKey = new Map<string, Unsubscriber>()
const unsubscribeGroupList = userGroupList.subscribe($userGroupList => {
const syncGroupList = ($userGroupList: List | undefined) => {
if ($userGroupList) {
const keys = new Set<string>()
@@ -226,43 +232,35 @@ const syncUserData = () => {
}
}
}
}
const syncRelayList = ($userRelayList: PublishedList | undefined) => {
const pubkey = $userRelayList?.event?.pubkey
if (!pubkey) return
loadBlossomServerList(pubkey)
loadBlockedRelayList(pubkey)
loadFollowList(pubkey)
loadGroupList(pubkey)
loadMuteList(pubkey)
loadProfile(pubkey)
loadSettings(pubkey)
loadFeedsForPubkey(pubkey)
}
const unsubscribeGroupList = merged([userGroupList]).subscribe(([$userGroupList]) => {
syncGroupList($userGroupList)
})
const unsubscribeRelayList = userRelayList.subscribe($userRelayList => {
if ($userRelayList) {
loadBlossomServerList($userRelayList.event.pubkey)
loadBlockedRelayList($userRelayList.event.pubkey)
loadFollowList($userRelayList.event.pubkey)
loadGroupList($userRelayList.event.pubkey)
loadMuteList($userRelayList.event.pubkey)
loadProfile($userRelayList.event.pubkey)
loadSettings($userRelayList.event.pubkey)
loadFeedsForPubkey($userRelayList.event.pubkey)
}
})
const unsubscribeFollows = userFollowList.subscribe(async $userFollowList => {
for (const pubkeys of chunk(10, get(bootstrapPubkeys))) {
// This isn't urgent, avoid clogging other stuff up
await sleep(1000)
await Promise.all(
pubkeys.flatMap(pk => [
loadRelayList(pk),
loadGroupList(pk),
loadProfile(pk),
loadFollowList(pk),
loadMuteList(pk),
]),
)
}
const unsubscribeRelayList = merged([userRelayList]).subscribe(([$userRelayList]) => {
syncRelayList($userRelayList)
})
return () => {
unsubscribersByKey.forEach(call)
unsubscribeGroupList()
unsubscribeRelayList()
unsubscribeFollows()
}
}
@@ -321,7 +319,7 @@ const syncSpace = (url: string, rooms: string[]) => {
}
const syncSpaces = () => {
const store = derived([userGroupList, page], identity)
const store = merged([userGroupList, page])
const unsubscribersByUrl = new Map<string, Unsubscriber>()
const roomsByUrl = new Map<string, string>()
@@ -383,6 +381,7 @@ const syncDMs = () => {
const unsubscribersByUrl = new Map<string, Unsubscriber>()
let currentPubkey: string | undefined
let currentShouldUnwrap = false
const unsubscribeAll = () => {
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
@@ -391,6 +390,34 @@ const syncDMs = () => {
}
}
const syncPubkey = ($pubkey: string | undefined, $shouldUnwrap: boolean) => {
if ($pubkey !== currentPubkey) {
unsubscribeAll()
}
if ($pubkey && $shouldUnwrap) {
loadRelayList($pubkey)
.then(() => loadMessagingRelayList($pubkey))
.then($l => {
if ($l && currentPubkey === $pubkey && currentShouldUnwrap === $shouldUnwrap) {
subscribeAll($pubkey, getRelayTagValues(getListTags($l)))
}
})
}
currentPubkey = $pubkey
currentShouldUnwrap = $shouldUnwrap
}
const syncList = ($userMessagingRelayList: List | undefined) => {
const $pubkey = pubkey.get()
const $shouldUnwrap = shouldUnwrap.get()
if ($pubkey && $shouldUnwrap) {
subscribeAll($pubkey, getRelayTagValues(getListTags($userMessagingRelayList)))
}
}
const subscribeAll = (pubkey: string, urls: string[]) => {
// Start syncing newly added relays
for (const url of urls) {
@@ -408,33 +435,16 @@ const syncDMs = () => {
}
}
// When pubkey changes, re-sync
const unsubscribePubkey = derived([pubkey, shouldUnwrap], identity).subscribe(
([$pubkey, $shouldUnwrap]) => {
if ($pubkey !== currentPubkey) {
unsubscribeAll()
}
// If we have a pubkey, refresh our user's relay list then sync our subscriptions
if ($pubkey && $shouldUnwrap) {
loadRelayList($pubkey)
.then(() => loadMessagingRelayList($pubkey))
.then($l => subscribeAll($pubkey, getRelayTagValues(getListTags($l))))
}
currentPubkey = $pubkey
},
)
const unsubscribePubkey = merged([pubkey, shouldUnwrap]).subscribe(([$pubkey, $shouldUnwrap]) => {
syncPubkey($pubkey, $shouldUnwrap)
})
// When user messaging relays change, update synchronization
const unsubscribeList = userMessagingRelayList.subscribe($userMessagingRelayList => {
const $pubkey = pubkey.get()
const $shouldUnwrap = shouldUnwrap.get()
if ($pubkey && $shouldUnwrap) {
subscribeAll($pubkey, getRelayTagValues(getListTags($userMessagingRelayList)))
}
})
const unsubscribeList = merged([userMessagingRelayList]).subscribe(
([$userMessagingRelayList]) => {
syncList($userMessagingRelayList)
},
)
return () => {
unsubscribeAll()
+2 -2
View File
@@ -12,8 +12,8 @@
</script>
<div class="btn flex h-[unset] w-full flex-nowrap py-4 text-left {props.class}">
<div class="flex flex-grow flex-row items-start gap-4">
<div class="flex h-14 w-12 flex-shrink-0 items-center justify-center">
<div class="flex grow flex-row items-start gap-4">
<div class="flex h-14 w-12 shrink-0 items-center justify-center">
{@render props.icon?.()}
</div>
<div class="flex flex-col gap-1">
+1 -1
View File
@@ -29,7 +29,7 @@
const innerClass = $derived(
cx(
"relative text-base-content text-base-content flex-grow pointer-events-auto",
"relative text-base-content text-base-content grow pointer-events-auto",
"rounded-t-box sm:rounded-box",
{
"bg-alt shadow-m max-h-[90vh] flex flex-col max-w-full pb-sai sm:pb-0": !fullscreen,
+2 -2
View File
@@ -9,9 +9,9 @@
</script>
<div class="flex items-center gap-2 p-2 text-xs uppercase opacity-50">
<div class="h-px flex-grow bg-base-content opacity-25"></div>
<div class="h-px grow bg-base-content opacity-25"></div>
{#if children}
<p>{@render children?.()}</p>
<div class="h-px flex-grow bg-base-content opacity-25"></div>
<div class="h-px grow bg-base-content opacity-25"></div>
{/if}
</div>
+2 -2
View File
@@ -8,9 +8,9 @@
const {children}: Props = $props()
</script>
<div class="h-20 flex-shrink-0"></div>
<div class="h-20 shrink-0"></div>
<div class="flex absolute bottom-sai left-0 right-0 p-6 py-4 rounded-b-box bg-base-200">
<div class="flex flex-grow gap-4 items-center justify-between">
<div class="flex grow gap-4 items-center justify-between">
{@render children?.()}
</div>
</div>
+1 -1
View File
@@ -9,6 +9,6 @@
<div
data-component="Page"
class="relative flex-grow flex flex-col min-w-0 ml-sai mb-sai mt-sai mr-sai bg-base-200 md:ml-0 md:mb-0 {props.class}">
class="relative grow flex flex-col min-w-0 ml-sai mb-sai mt-sai mr-sai bg-base-200 md:ml-0 md:mb-0 {props.class}">
{@render props.children?.()}
</div>
+1 -1
View File
@@ -12,7 +12,7 @@
<div
class={cx(
"mt-sai mb-sai max-h-screen w-60 min-h-0 flex-shrink-0 flex-col gap-1 bg-base-300 z-nav hidden md:flex",
"mt-sai mb-sai max-h-screen w-60 min-h-0 shrink-0 flex-col gap-1 bg-base-300 z-nav hidden md:flex",
props.class,
)}>
{@render children?.()}
+1 -1
View File
@@ -36,7 +36,7 @@
const active = $derived($page.url.pathname === href)
const wrapperClass = $derived(
cx(restProps.class, "relative flex flex-shrink-0 items-center gap-3 text-left transition-all", {
cx(restProps.class, "relative flex shrink-0 items-center gap-3 text-left transition-all", {
"hover:bg-base-100 hover:text-base-content": true,
"text-base-content bg-base-100": active,
"tooltip tooltip-right": title,
+6 -6
View File
@@ -1,7 +1,7 @@
<style>
:global(.tippy-box[data-theme~="tooltip"]) {
background-color: var(--neutral);
color: var(--neutral-content);
background-color: var(--color-neutral);
color: var(--color-neutral-content);
border-radius: 0.5rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
@@ -11,19 +11,19 @@
}
:global(.tippy-box[data-theme~="tooltip"][data-placement^="top"] > .tippy-arrow::before) {
border-top-color: var(--neutral);
border-top-color: var(--color-neutral);
}
:global(.tippy-box[data-theme~="tooltip"][data-placement^="bottom"] > .tippy-arrow::before) {
border-bottom-color: var(--neutral);
border-bottom-color: var(--color-neutral);
}
:global(.tippy-box[data-theme~="tooltip"][data-placement^="left"] > .tippy-arrow::before) {
border-left-color: var(--neutral);
border-left-color: var(--color-neutral);
}
:global(.tippy-box[data-theme~="tooltip"][data-placement^="right"] > .tippy-arrow::before) {
border-right-color: var(--neutral);
border-right-color: var(--color-neutral);
}
</style>
+1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import "@src/app.css"
import "@welshman/editor/index.css"
import "@capacitor-community/safe-area"
import * as nip19 from "nostr-tools/nip19"
import type {Unsubscriber} from "svelte/store"
+2 -3
View File
@@ -6,7 +6,6 @@
import {shouldUnwrap} from "@welshman/app"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import ChatSquarePlus from "@assets/icons/chat-square-plus.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Page from "@lib/components/Page.svelte"
@@ -51,8 +50,8 @@
<Icon icon={MenuDots} />
</Button>
</SecondaryNavHeader>
<Button class="btn btn-primary w-full row-2 min-h-0 h-[30px]" onclick={startChat}>
<Icon icon={AddCircle} />
<Button class="btn btn-primary w-full btn-sm" onclick={startChat}>
<Icon icon={ChatSquarePlus} />
Start New Chat
</Button>
<label class="input input-sm input-bordered flex items-center gap-2">
+3 -3
View File
@@ -37,8 +37,8 @@
<ContentSearch class="md:hidden">
{#snippet input()}
<div class="row-2 min-w-0 flex-grow items-center">
<label class="input input-bordered flex flex-grow items-center gap-2">
<div class="row-2 min-w-0 grow items-center">
<label class="input input-bordered flex grow items-center gap-2">
<Icon icon={Magnifier} />
<input
bind:value={term}
@@ -46,7 +46,7 @@
type="text"
placeholder="Search for conversations..." />
</label>
<Button class="btn btn-primary" onclick={openMenu}>
<Button class="btn btn-neutral" onclick={openMenu}>
<Icon icon={MenuDots} />
</Button>
</div>
+5 -2
View File
@@ -12,6 +12,7 @@
import Bell from "@assets/icons/bell.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Page from "@lib/components/Page.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
@@ -32,7 +33,7 @@
<SecondaryNav>
<SecondaryNavSection>
<SecondaryNavItem class="w-full !justify-between">
<SecondaryNavItem class="w-full justify-between!">
<strong class="ellipsize flex items-center gap-3"> Your Settings </strong>
</SecondaryNavItem>
<SecondaryNavItem href="/settings/profile">
@@ -68,5 +69,7 @@
</SecondaryNav>
<Page>
{@render children?.()}
<PageContent>
{@render children?.()}
</PageContent>
</Page>
+3 -3
View File
@@ -67,7 +67,7 @@
</script>
<div class="content column gap-4">
<div class="card2 bg-alt shadow-md">
<div class="card2 bg-alt shadow-md col-2">
<div class="flex justify-between gap-2">
<div class="flex max-w-full gap-3">
<div class="py-1">
@@ -116,9 +116,9 @@
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center justify-between gap-2">
<div class="row-2 flex-grow items-center">
<div class="row-2 grow items-center">
<Icon icon={LinkRound} />
<input readonly class="ellipsize flex-grow" value={npub} />
<input readonly class="ellipsize grow" value={npub} />
</div>
<Button class="flex items-center" onclick={copyNpub}>
<Icon icon={Copy} />
+2 -2
View File
@@ -22,10 +22,10 @@
<svelte:window bind:innerWidth={width} />
{#if width <= md}
<div class="ml-sai mt-sai mb-sai relative z-nav w-14 flex-shrink-0 bg-base-200 pt-2">
<div class="ml-sai mt-sai mb-sai relative z-nav w-14 shrink-0 bg-base-200 pt-2">
<PrimaryNavSpaces />
</div>
<SecondaryNav class="!flex !w-auto flex-grow pb-16">
<SecondaryNav class="flex! w-auto! grow pb-16">
<SpaceMenu {url} />
</SecondaryNav>
{/if}
+3 -3
View File
@@ -398,9 +398,9 @@
{id}
class="flex items-center py-2 text-xs transition-colors"
class:opacity-0={showFixedNewMessages}>
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
<p class="rounded-full bg-primary px-2 py-1 text-primary-content">New Messages</p>
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
</div>
{:else if type === "date"}
<Divider>{value}</Divider>
@@ -481,7 +481,7 @@
{/if}
</div>
{#if isVoiceRoom || $voiceState === VoiceState.Joining || $voiceState === VoiceState.Connected}
<div class="hide-on-keyboard flex-shrink-0 p-2 md:hidden">
<div class="hide-on-keyboard shrink-0 p-2 md:hidden">
<VoiceWidget />
</div>
{/if}
@@ -128,9 +128,9 @@
<div class={"calendar-event-" + event.id}>
{#if isFirstFutureEvent}
<div class="flex items-center gap-2 p-2">
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
<p class="text-xs uppercase text-primary">Today</p>
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
</div>
{/if}
{#if dateDisplay}
@@ -69,11 +69,11 @@
<div class="card2 bg-alt col-3 z-feature">
<div class="flex items-start gap-4">
<CalendarEventDate event={$event} />
<div class="flex min-w-0 flex-grow flex-col gap-1">
<div class="flex min-w-0 grow flex-col gap-1">
<CalendarEventHeader event={$event} />
<CalendarEventMeta event={$event} {url} />
<div class="flex py-2 opacity-50">
<div class="h-px flex-grow bg-base-content opacity-25"></div>
<div class="h-px grow bg-base-content opacity-25"></div>
</div>
<Content showEntire event={$event} {url} />
</div>
+2 -2
View File
@@ -311,9 +311,9 @@
{id}
class="flex items-center py-2 text-xs transition-colors"
class:opacity-0={showFixedNewMessages}>
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
<p class="rounded-full bg-primary px-2 py-1 text-primary-content">New Messages</p>
<div class="h-px flex-grow bg-primary"></div>
<div class="h-px grow bg-primary"></div>
</div>
{:else if type === "date"}
<Divider>{value}</Divider>
+27 -24
View File
@@ -1,6 +1,7 @@
import {config} from "dotenv"
import daisyui from "daisyui"
import themes from "daisyui/src/theming/themes"
import daisyTheme from "daisyui/theme"
import themes from "daisyui/theme/object"
config({path: ".env.local"})
config({path: ".env"})
@@ -25,27 +26,29 @@ export default {
toast: 9,
},
},
plugins: [daisyui],
daisyui: {
themes: [
{
dark: {
...themes["dark"],
primary: process.env.VITE_PLATFORM_ACCENT,
"primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF",
secondary: process.env.VITE_PLATFORM_SECONDARY,
"secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF",
},
light: {
...themes["winter"],
neutral: "#F2F7FF",
warning: "#FD8D0B",
primary: process.env.VITE_PLATFORM_ACCENT,
"primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF",
secondary: process.env.VITE_PLATFORM_SECONDARY,
"secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF",
},
},
],
},
plugins: [
daisyui({
themes: ["light --default", "dark --prefersdark"],
}),
daisyTheme({
name: "dark",
...themes["night"],
"--color-base-content": "oklch(75% 0.029 256.847)",
"--color-primary": process.env.VITE_PLATFORM_ACCENT,
"--color-primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF",
"--color-secondary": process.env.VITE_PLATFORM_SECONDARY,
"--color-secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF",
}),
daisyTheme({
name: "light",
...themes["winter"],
"--color-neutral": "#F2F7FF",
"--color-neutral-content": "var(--color-base-content)",
"--color-warning": "#FD8D0B",
"--color-primary": process.env.VITE_PLATFORM_ACCENT,
"--color-primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF",
"--color-secondary": process.env.VITE_PLATFORM_SECONDARY,
"--color-secondary-content": process.env.VITE_PLATFORM_SECONDARY_CONTENT || "#EAE7FF",
}),
],
}