Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81ab03311a | |||
| 11fa8fd720 | |||
| 3cff8271e6 | |||
| 2c4fe7bcf3 | |||
| fb60d493b9 | |||
| 39ce62d903 | |||
| 4d90092f4b | |||
| ceca21e867 | |||
| 7353dab8c2 | |||
| 31d7041e5c | |||
| b545f225a5 | |||
| 1ccd6070df | |||
| 2661f449dd | |||
| 9e4ac16673 | |||
| 236b9e011e | |||
| 8f6f628bd7 | |||
| 7c27846d0d | |||
| 9b080996d0 | |||
| 4e23fb3bba | |||
| 9f6b16089b | |||
| 65ca8a7fd8 | |||
| 7f1e98dcb2 | |||
| 4c19ee823b |
@@ -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_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_SIGNER_RELAYS=relay.nsec.app,ephemeral.snowflare.cc,bucket.coracle.social
|
||||||
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
|
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
|
||||||
|
VITE_THUMBNAIL_URL=
|
||||||
VITE_GLITCHTIP_API_KEY=
|
VITE_GLITCHTIP_API_KEY=
|
||||||
GLITCHTIP_AUTH_TOKEN=
|
GLITCHTIP_AUTH_TOKEN=
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ node_modules/
|
|||||||
.pnpm-store/
|
.pnpm-store/
|
||||||
build/
|
build/
|
||||||
.svelte-kit/
|
.svelte-kit/
|
||||||
|
.next/
|
||||||
|
|
||||||
# Rust/Tauri
|
# Rust/Tauri
|
||||||
*target/
|
*target/
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
@config "../tailwind.config.js";
|
@config "../tailwind.config.js";
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@utility content-padding-y {
|
@utility content-padding-y {
|
||||||
@apply pt-4 sm:pt-8 md:pt-12 pb-4 sm:pb-8 md:pb-12;
|
@apply pt-4 pb-4 sm:pt-8 sm:pb-8 md:pt-12 md:pb-12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility content-sizing {
|
@utility content-sizing {
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@utility content {
|
@utility content {
|
||||||
@apply m-auto w-full max-w-3xl px-4 sm:px-8 md:px-12 pt-4 sm:pt-8 md:pt-12 pb-4 sm:pb-8 md:pb-12;
|
@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 {
|
@utility heading {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {call, ellipsize, displayUrl, postJson} from "@welshman/lib"
|
import {call, ellipsize, displayUrl, postJson} from "@welshman/lib"
|
||||||
import {isRelayUrl, getTagValue} from "@welshman/util"
|
import {isRelayUrl, getTagValue} from "@welshman/util"
|
||||||
|
import {Capacitor} from "@capacitor/core"
|
||||||
import {preventDefault, stopPropagation} from "@lib/html"
|
import {preventDefault, stopPropagation} from "@lib/html"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
||||||
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
|
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
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"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
|
|
||||||
const {value, event} = $props()
|
const {value, event} = $props()
|
||||||
@@ -22,6 +29,14 @@
|
|||||||
return [url, true]
|
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 loadPreview = async () => {
|
||||||
const json = await postJson(dufflepud("link/preview"), {url})
|
const json = await postJson(dufflepud("link/preview"), {url})
|
||||||
|
|
||||||
@@ -42,7 +57,12 @@
|
|||||||
<Link {external} {href} class="my-2 block">
|
<Link {external} {href} class="my-2 block">
|
||||||
<div class="overflow-hidden rounded-box">
|
<div class="overflow-hidden rounded-box">
|
||||||
{#if url.match(/\.(mov|webm|mp4)$/) || VIDEO_CONTENT_TYPES.includes(fileType)}
|
{#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" />
|
<track kind="captions" />
|
||||||
</video>
|
</video>
|
||||||
{:else if url.match(/\.(jpe?g|png|gif|webp)$/) || IMAGE_CONTENT_TYPES.includes(fileType)}
|
{:else if url.match(/\.(jpe?g|png|gif|webp)$/) || IMAGE_CONTENT_TYPES.includes(fileType)}
|
||||||
|
|||||||
@@ -62,8 +62,7 @@
|
|||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|
||||||
<!-- a little extra something for ios -->
|
<!-- a little extra something for ios -->
|
||||||
<div
|
<div class="hide-on-keyboard fixed bottom-0 left-0 right-0 z-nav h-(--saib) bg-base-100 md:hidden">
|
||||||
class="hide-on-keyboard fixed bottom-0 left-0 right-0 z-nav h-(--saib) bg-base-100 md:hidden">
|
|
||||||
</div>
|
</div>
|
||||||
<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">
|
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">
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ export const DEFAULT_PUBKEYS = import.meta.env.VITE_DEFAULT_PUBKEYS
|
|||||||
|
|
||||||
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
||||||
|
|
||||||
|
export const THUMBNAIL_URL = import.meta.env.VITE_THUMBNAIL_URL
|
||||||
|
|
||||||
export const NIP46_PERMS =
|
export const NIP46_PERMS =
|
||||||
"nip44_encrypt,nip44_decrypt," +
|
"nip44_encrypt,nip44_decrypt," +
|
||||||
[
|
[
|
||||||
|
|||||||
+74
-64
@@ -1,8 +1,8 @@
|
|||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import type {Unsubscriber} from "svelte/store"
|
import type {Unsubscriber} from "svelte/store"
|
||||||
import {derived, get} from "svelte/store"
|
import {last, call, ifLet, assoc, chunk, WEEK, ago} from "@welshman/lib"
|
||||||
import {last, call, ifLet, assoc, chunk, sleep, identity, WEEK, ago} from "@welshman/lib"
|
|
||||||
import {PollResponse} from "nostr-tools/kinds"
|
import {PollResponse} from "nostr-tools/kinds"
|
||||||
|
import {merged} from "@welshman/store"
|
||||||
import {
|
import {
|
||||||
getListTags,
|
getListTags,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
@@ -21,12 +21,11 @@ import {
|
|||||||
unionFilters,
|
unionFilters,
|
||||||
getTagValue,
|
getTagValue,
|
||||||
} from "@welshman/util"
|
} 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 {request, requestOne, Difference, DifferenceEvent} from "@welshman/net"
|
||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
loadRelay,
|
loadRelay,
|
||||||
userFollowList,
|
|
||||||
userRelayList,
|
userRelayList,
|
||||||
userMessagingRelayList,
|
userMessagingRelayList,
|
||||||
loadRelayList,
|
loadRelayList,
|
||||||
@@ -49,7 +48,6 @@ import {
|
|||||||
loadGroupList,
|
loadGroupList,
|
||||||
userSpaceUrls,
|
userSpaceUrls,
|
||||||
userGroupList,
|
userGroupList,
|
||||||
bootstrapPubkeys,
|
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
getSpaceUrlsFromGroupList,
|
getSpaceUrlsFromGroupList,
|
||||||
getSpaceRoomsFromGroupList,
|
getSpaceRoomsFromGroupList,
|
||||||
@@ -74,6 +72,8 @@ const pullOneWithFallback = async (
|
|||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
onEvent?: (event: TrustedEvent) => void,
|
onEvent?: (event: TrustedEvent) => void,
|
||||||
) => {
|
) => {
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
const cachedEvents = repository.query([filter]).filter(isSignedEvent)
|
const cachedEvents = repository.query([filter]).filter(isSignedEvent)
|
||||||
const since = last(cachedEvents.slice(10))?.created_at || 0
|
const since = last(cachedEvents.slice(10))?.created_at || 0
|
||||||
|
|
||||||
@@ -86,6 +86,12 @@ const pullOneWithFallback = async (
|
|||||||
const shouldFallback =
|
const shouldFallback =
|
||||||
!hasNegentropy(url) ||
|
!hasNegentropy(url) ||
|
||||||
(await new Promise(resolve => {
|
(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})
|
const diff = new Difference({relay: url, filter, events: cachedEvents, signal})
|
||||||
|
|
||||||
diff.on(DifferenceEvent.Error, () => {
|
diff.on(DifferenceEvent.Error, () => {
|
||||||
@@ -111,9 +117,7 @@ export const pullWithFallback = async ({url, signal, filters, onEvent}: SyncOpts
|
|||||||
|
|
||||||
if (signal.aborted) return
|
if (signal.aborted) return
|
||||||
|
|
||||||
for (const filter of filters) {
|
await Promise.all(filters.map(filter => pullOneWithFallback(url, filter, signal, onEvent)))
|
||||||
pullOneWithFallback(url, filter, signal, onEvent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
|
const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
|
||||||
@@ -123,6 +127,8 @@ const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pullAndListen = (options: SyncOpts) => {
|
const pullAndListen = (options: SyncOpts) => {
|
||||||
|
if (options.signal.aborted) return
|
||||||
|
|
||||||
pullWithFallback(options)
|
pullWithFallback(options)
|
||||||
listen(options)
|
listen(options)
|
||||||
}
|
}
|
||||||
@@ -197,7 +203,7 @@ const syncUserRoomMembership = (url: string, h: string) => {
|
|||||||
const syncUserData = () => {
|
const syncUserData = () => {
|
||||||
const unsubscribersByKey = new Map<string, Unsubscriber>()
|
const unsubscribersByKey = new Map<string, Unsubscriber>()
|
||||||
|
|
||||||
const unsubscribeGroupList = userGroupList.subscribe($userGroupList => {
|
const syncGroupList = ($userGroupList: List | undefined) => {
|
||||||
if ($userGroupList) {
|
if ($userGroupList) {
|
||||||
const keys = new Set<string>()
|
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 => {
|
const unsubscribeRelayList = merged([userRelayList]).subscribe(([$userRelayList]) => {
|
||||||
if ($userRelayList) {
|
syncRelayList($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),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribersByKey.forEach(call)
|
unsubscribersByKey.forEach(call)
|
||||||
unsubscribeGroupList()
|
unsubscribeGroupList()
|
||||||
unsubscribeRelayList()
|
unsubscribeRelayList()
|
||||||
unsubscribeFollows()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +319,7 @@ const syncSpace = (url: string, rooms: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const syncSpaces = () => {
|
const syncSpaces = () => {
|
||||||
const store = derived([userGroupList, page], identity)
|
const store = merged([userGroupList, page])
|
||||||
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
||||||
const roomsByUrl = new Map<string, string>()
|
const roomsByUrl = new Map<string, string>()
|
||||||
|
|
||||||
@@ -383,6 +381,7 @@ const syncDMs = () => {
|
|||||||
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
||||||
|
|
||||||
let currentPubkey: string | undefined
|
let currentPubkey: string | undefined
|
||||||
|
let currentShouldUnwrap = false
|
||||||
|
|
||||||
const unsubscribeAll = () => {
|
const unsubscribeAll = () => {
|
||||||
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
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[]) => {
|
const subscribeAll = (pubkey: string, urls: string[]) => {
|
||||||
// Start syncing newly added relays
|
// Start syncing newly added relays
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
@@ -408,33 +435,16 @@ const syncDMs = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When pubkey changes, re-sync
|
const unsubscribePubkey = merged([pubkey, shouldUnwrap]).subscribe(([$pubkey, $shouldUnwrap]) => {
|
||||||
const unsubscribePubkey = derived([pubkey, shouldUnwrap], identity).subscribe(
|
syncPubkey($pubkey, $shouldUnwrap)
|
||||||
([$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
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// When user messaging relays change, update synchronization
|
// When user messaging relays change, update synchronization
|
||||||
const unsubscribeList = userMessagingRelayList.subscribe($userMessagingRelayList => {
|
const unsubscribeList = merged([userMessagingRelayList]).subscribe(
|
||||||
const $pubkey = pubkey.get()
|
([$userMessagingRelayList]) => {
|
||||||
const $shouldUnwrap = shouldUnwrap.get()
|
syncList($userMessagingRelayList)
|
||||||
|
},
|
||||||
if ($pubkey && $shouldUnwrap) {
|
)
|
||||||
subscribeAll($pubkey, getRelayTagValues(getListTags($userMessagingRelayList)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeAll()
|
unsubscribeAll()
|
||||||
|
|||||||
Reference in New Issue
Block a user