From 2039bbace85dbfe1c0131f4ce91402f94edffd98 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 5 Nov 2024 17:03:20 -0800 Subject: [PATCH 1/4] Remove options from feed --- src/app/shared/Feed.svelte | 14 ++-------- src/app/util/feeds.ts | 45 ++++++++------------------------ src/app/views/RelayDetail.svelte | 21 +++++---------- src/engine/state.ts | 22 ---------------- 4 files changed, 20 insertions(+), 82 deletions(-) diff --git a/src/app/shared/Feed.svelte b/src/app/shared/Feed.svelte index 5bea03393..910a647d8 100644 --- a/src/app/shared/Feed.svelte +++ b/src/app/shared/Feed.svelte @@ -17,13 +17,9 @@ export let feed: Feed export let anchor = null - export let eager = false - export let skipNetwork = false - export let forcePlatform = true export let showControls = false + export let forcePlatform = true export let hideSpinner = false - export let includeReposts = false - export let onEvent = null const splits = [["zap", env.PLATFORM_PUBKEY, "", "1"]] @@ -35,15 +31,9 @@ limit = 0 loader?.stop() loader = createFeed({ - anchor, - onEvent, - skipNetwork, - forcePlatform, - includeReposts, - shouldDefer: !eager, - shouldLoadParents: true, shouldHideReplies: $shouldHideReplies, feed: feed.definition, + forcePlatform, }) } diff --git a/src/app/util/feeds.ts b/src/app/util/feeds.ts index a4ca05877..86a25e5d6 100644 --- a/src/app/util/feeds.ts +++ b/src/app/util/feeds.ts @@ -25,33 +25,19 @@ import { sortEventsDesc, unwrapRepost, isEventMuted, - addRepostFilters, load, } from "src/engine" export type FeedOpts = { feed?: Feed - anchor?: string - skipNetwork?: boolean forcePlatform?: boolean - shouldDefer?: boolean shouldHideReplies?: boolean - shouldLoadParents?: boolean - includeReposts?: boolean - onEvent?: (e: TrustedEvent) => void + // Deprecated + anchor?: string } -const prepFilters = (filters, opts: FeedOpts) => { - // Default to note kinds - filters = filters?.map(filter => ({kinds: noteKinds, ...filter})) || [] - - // Add reposts if we don't have any authors specified - if (opts.includeReposts && !filters.some(f => f.authors?.length > 0)) { - filters = addRepostFilters(filters) - } - - return filters -} +const prepFilters = (filters, opts: FeedOpts) => + filters?.map(filter => ({kinds: noteKinds, ...filter})) || [] function* getRequestItems({relays, filters}: RequestItem, opts: FeedOpts) { filters = prepFilters(filters, opts) @@ -59,7 +45,7 @@ function* getRequestItems({relays, filters}: RequestItem, opts: FeedOpts) { // Use relays specified in feeds if (relays?.length > 0) { yield {filters, relays} - } else if (!opts.skipNetwork) { + } else { yield* getFilterSelections(filters) } } @@ -70,19 +56,17 @@ const createFeedLoader = (opts: FeedOpts, signal) => ...baseFeedLoader.options, request: async ({relays, filters, onEvent}) => { const tracker = new Tracker() - const forceRelays = relays?.length > 0 - const forcePlatform = opts.forcePlatform && !forceRelays const requestItems = Array.from(getRequestItems({relays, filters}, opts)) await Promise.all( - requestItems.map(opts => + requestItems.map(item => load({ - ...opts, + ...item, onEvent, tracker, signal, skipCache: true, - forcePlatform, + forcePlatform: opts.forcePlatform && relays?.length === 0, }), ), ) @@ -90,7 +74,7 @@ const createFeedLoader = (opts: FeedOpts, signal) => // Wait until after we've queried the network to access our local cache. This results in less // snappy response times, but is necessary to prevent stale stuff that the user has already seen // from showing up at the top of the feed - if (!forceRelays) { + if (relays.length === 0) { for (const event of repository.query(prepFilters(filters, opts))) { onEvent(event) } @@ -122,9 +106,7 @@ export const createFeed = (opts: FeedOpts) => { const keep = discardEvents(events) - if (opts.shouldLoadParents) { - loadParents(keep) - } + loadParents(keep) const withoutOrphans = deferOrphans(keep) const withoutAncient = deferAncient(withoutOrphans) @@ -147,10 +129,6 @@ export const createFeed = (opts: FeedOpts) => { const sortEvents = (events: TrustedEvent[]) => (useWindowing ? sortEventsDesc(events) : events) function deferOrphans(events: TrustedEvent[]) { - if (!opts.shouldLoadParents || opts.shouldDefer === false) { - return events - } - // If something has a parent id but we haven't found the parent yet, skip it until we have it. const [ok, defer] = partition(e => { const parentIds = Tags.fromEvent(e).parents().values().valueOf() @@ -170,7 +148,7 @@ export const createFeed = (opts: FeedOpts) => { } function deferAncient(events: TrustedEvent[]) { - if (opts.shouldDefer === false || !useWindowing) { + if (!useWindowing) { return events } @@ -329,7 +307,6 @@ export const createFeed = (opts: FeedOpts) => { if (e.kind === REACTION && !isLike(e)) return false if ([4, DIRECT_MESSAGE].includes(e.kind)) return false if (opts.shouldHideReplies && Tags.fromEvent(e).parent()) return false - if (getIdOrAddress(e) === opts.anchor) return false if ($isEventMuted(e, true)) return false return true diff --git a/src/app/views/RelayDetail.svelte b/src/app/views/RelayDetail.svelte index 539d160d1..83f90ae2e 100644 --- a/src/app/views/RelayDetail.svelte +++ b/src/app/views/RelayDetail.svelte @@ -1,8 +1,8 @@ @@ -59,7 +52,7 @@ {/if} {#if activeTab === "reviews"} - + {:else} {/if} diff --git a/src/engine/state.ts b/src/engine/state.ts index 396de8f2e..228dc3406 100644 --- a/src/engine/state.ts +++ b/src/engine/state.ts @@ -56,7 +56,6 @@ import {Nip01Signer, Nip59} from "@welshman/signer" import {deriveEvents, deriveEventsMapped, throttled, withGetter} from "@welshman/store" import type { EventTemplate, - Filter, PublishedList, SignedEvent, StampedEvent, @@ -713,27 +712,6 @@ export const collectionSearch = derived( // Network -export const addRepostFilters = (filters: Filter[]) => - filters.flatMap(original => { - const filterChunk = [original] - - if (!original.kinds) { - filterChunk.push({...original, kinds: [6, 16]}) - } else { - if (original.kinds.includes(1)) { - filterChunk.push({...original, kinds: [6]}) - } - - const otherKinds = without([1], original.kinds) - - if (otherKinds.length > 0) { - filterChunk.push({...original, kinds: [16], "#k": otherKinds.map(String)}) - } - } - - return filterChunk - }) - export const getExecutor = (urls: string[]) => { const [localUrls, remoteUrls] = partition(equals(LOCAL_RELAY_URL), urls) From 3ff18b6b3e32cb477449b395015f6ec1325f1806 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 7 Nov 2024 09:11:23 -0800 Subject: [PATCH 2/4] Re-write feeds --- package-lock.json | 42 ++--- package.json | 6 +- src/app/shared/Feed.svelte | 180 +++++++++++++++--- src/app/shared/Note.svelte | 3 +- src/app/shared/NoteMeta.svelte | 8 +- src/app/util/feeds.ts | 322 --------------------------------- src/app/util/index.ts | 1 - src/engine/model.ts | 5 - src/engine/requests.ts | 133 +++++--------- 9 files changed, 237 insertions(+), 463 deletions(-) delete mode 100644 src/app/util/feeds.ts diff --git a/package-lock.json b/package-lock.json index f92ec5af2..cd65443d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,15 +27,15 @@ "@types/ramda": "^0.30.2", "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", - "@welshman/app": "~0.0.21", + "@welshman/app": "~0.0.22", "@welshman/content": "~0.0.12", "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.21", + "@welshman/feeds": "~0.0.22", "@welshman/lib": "~0.0.24", "@welshman/net": "~0.0.33", "@welshman/signer": "~0.0.11", "@welshman/store": "~0.0.12", - "@welshman/util": "~0.0.44", + "@welshman/util": "~0.0.45", "autoprefixer": "^10.4.20", "bowser": "^2.11.0", "classnames": "^2.5.1", @@ -4775,18 +4775,18 @@ } }, "node_modules/@welshman/app": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.21.tgz", - "integrity": "sha512-dg/dK0JSzb9YiSPKXopQUhM+tpduw1Z7lnQUEbYyO2YvTieNXaWRPCnGTWPVnyA/iokQ2E18kPph5OD/hWVJkA==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.22.tgz", + "integrity": "sha512-0qC3mXIIorpBndXSYV9w160woN05ytPWeJreXbJH+Xki7myPpgMf18dMZR/8z5iIJ3UvIG14XQNb2nOqZxmt4Q==", "license": "MIT", "dependencies": { "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.20", - "@welshman/lib": "~0.0.23", - "@welshman/net": "~0.0.30", - "@welshman/signer": "~0.0.10", - "@welshman/store": "~0.0.11", - "@welshman/util": "~0.0.42", + "@welshman/feeds": "~0.0.22", + "@welshman/lib": "~0.0.24", + "@welshman/net": "~0.0.33", + "@welshman/signer": "~0.0.11", + "@welshman/store": "~0.0.12", + "@welshman/util": "~0.0.45", "fuse.js": "^7.0.0", "idb": "^8.0.0", "svelte": "^4.2.18", @@ -4816,13 +4816,13 @@ } }, "node_modules/@welshman/feeds": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.21.tgz", - "integrity": "sha512-ssxHEQdYkXpSTBXL6NiXr+Cs7tat00K4ZS4YvoVIbV+PJgKW2e5NG6LKwZJUh7NzwVW+TjYUKlmiJQbGocTpFQ==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.22.tgz", + "integrity": "sha512-7ysWrTXX2Eh0yvBKSIyVdX4moajHLYOlLjU/fq64oV9UPyJA77hniuJWrI9Qer9wChL+u2S6nY/7eaLprPiY/w==", "license": "MIT", "dependencies": { - "@welshman/lib": "~0.0.23", - "@welshman/util": "~0.0.42" + "@welshman/lib": "~0.0.24", + "@welshman/util": "~0.0.45" } }, "node_modules/@welshman/lib": { @@ -4877,12 +4877,12 @@ } }, "node_modules/@welshman/util": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.44.tgz", - "integrity": "sha512-pTnm2d54InKVhtQY9euvwhNdTOZyJVhobok2SuAcLNITn928+ORgRjQdlWePX5gahkjuczjpfK98Sor14ou0Pg==", + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.45.tgz", + "integrity": "sha512-6ktWY/LQsBqVYN+PIfT7Aob48QDf4XjXOI1aDJ7tsewaG4FKuMDy9rfHE7/FjVMGhYrKsugaL2GBVFcS4UfRTg==", "license": "MIT", "dependencies": { - "@welshman/lib": "~0.0.23", + "@welshman/lib": "~0.0.24", "nostr-tools": "^2.7.2" } }, diff --git a/package.json b/package.json index 163260411..93444bbc1 100644 --- a/package.json +++ b/package.json @@ -40,15 +40,15 @@ "@types/ramda": "^0.30.2", "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", - "@welshman/app": "~0.0.21", + "@welshman/app": "~0.0.22", "@welshman/content": "~0.0.12", "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.21", + "@welshman/feeds": "~0.0.22", "@welshman/lib": "~0.0.24", "@welshman/net": "~0.0.33", "@welshman/signer": "~0.0.11", "@welshman/store": "~0.0.12", - "@welshman/util": "~0.0.44", + "@welshman/util": "~0.0.45", "autoprefixer": "^10.4.20", "bowser": "^2.11.0", "classnames": "^2.5.1", diff --git a/src/app/shared/Feed.svelte b/src/app/shared/Feed.svelte index 910a647d8..12c6e0058 100644 --- a/src/app/shared/Feed.svelte +++ b/src/app/shared/Feed.svelte @@ -2,8 +2,27 @@ import {onMount} from "svelte" import {seconds} from "hurdak" import {writable} from "svelte/store" - import {now, hash} from "@welshman/lib" + import {ctx, now, identity, uniqBy, range, hash, pushToMapKey} from "@welshman/lib" + import { + neverFilter, + getIdFilters, + REACTION, + getIdOrAddress, + getIdAndAddress, + getAncestorTagValues, + } from "@welshman/util" + import type {Filter, TrustedEvent} from "@welshman/util" + import type {FeedController, Feed as FeedDefinition} from "@welshman/feeds" + import { + isRelayFeed, + makeKindFeed, + makeIntersectionFeed, + isKindFeed, + walkFeed, + } from "@welshman/feeds" + import {repository} from "@welshman/app" import {createScroller, synced} from "src/util/misc" + import {noteKinds, repostKinds, reactionKinds, isLike} from "src/util/nostr" import {fly, fade} from "src/util/transition" import Anchor from "src/partials/Anchor.svelte" import Card from "src/partials/Card.svelte" @@ -11,9 +30,16 @@ import FlexColumn from "src/partials/FlexColumn.svelte" import Note from "src/app/shared/Note.svelte" import FeedControls from "src/app/shared/FeedControls.svelte" - import {createFeed, router} from "src/app/util" + import {router} from "src/app/util" import type {Feed} from "src/domain" - import {env} from "src/engine" + import { + env, + load, + createFeedController, + sortEventsDesc, + isEventMuted, + unwrapRepost, + } from "src/engine" export let feed: Feed export let anchor = null @@ -21,20 +47,68 @@ export let forcePlatform = true export let hideSpinner = false + const reposts = new Map() + const splits = [["zap", env.PLATFORM_PUBKEY, "", "1"]] const promptDismissed = synced("feed/promptDismissed", 0) const shouldHideReplies = showControls ? synced("Feed.shouldHideReplies", false) : writable(false) - const reload = async () => { - limit = 0 - loader?.stop() - loader = createFeed({ - shouldHideReplies: $shouldHideReplies, - feed: feed.definition, + const reload = () => { + exhausted = false + useWindowing = true + events = [] + buffer = [] + filters = [neverFilter] + + let hasKinds = false + + walkFeed(feed.definition, (subFeed: FeedDefinition) => { + hasKinds = hasKinds || isKindFeed(subFeed) + useWindowing = useWindowing && !isRelayFeed(subFeed) + }) + + const definition = hasKinds + ? feed.definition + : makeIntersectionFeed(makeKindFeed(...noteKinds), feed.definition) + + ctrl = createFeedController({ + feed: definition, forcePlatform, + useWindowing, + onEvent: async e => { + if (e.kind === REACTION && !isLike(e)) return false + if (repository.isDeleted(e)) return false + if ($isEventMuted(e, true)) return false + + const {replies} = getAncestorTagValues(e.tags) + + if ($shouldHideReplies && replies.length > 0) return false + + if (replies.length > 0 && !replies.find(id => repository.getEvent(id))) { + await load({ + filters: getIdFilters(replies), + relays: ctx.app.router.EventParents(e).getUrls(), + }) + } + + buffer.push(e) + }, + onExhausted: () => { + exhausted = true + }, + }) + + ctrl.getFilters().then($filters => { + if ($filters) { + filters = $filters + } }) + + if (!useWindowing) { + ctrl.load(1000) + } } const toggleReplies = () => { @@ -47,24 +121,88 @@ reload() } + const getNewEvents = () => { + const seen = new Set(events.map(getIdOrAddress)) + + const isSeen = (e: TrustedEvent) => { + if (getIdAndAddress(e).some(v => seen.has(v))) return true + if (getAncestorTagValues(e.tags).replies.some(v => seen.has(v))) return true + + return false + } + + const unwrap = (e: TrustedEvent) => { + if (repostKinds.includes(e.kind)) { + const wrappedEvent = unwrapRepost(e) + + if (wrappedEvent) { + pushToMapKey(reposts, wrappedEvent.id, e) + e = wrappedEvent + } + } + + return e + } + + return buffer + .splice(0, 10) + .map((e: TrustedEvent) => { + // If we have a repost, use its contents instead + e = unwrap(e) + + if (isSeen(e)) return undefined + + Array.from(range(0, 2)).forEach(() => { + const parent = getAncestorTagValues(e.tags) + .replies.map(v => repository.getEvent(v)) + .find(identity) + + // If we have a parent, show that instead, with replies grouped underneath + if (parent) { + e = unwrap(parent) + } + }) + + if (isSeen(e)) return undefined + if (repostKinds.includes(e.kind)) return undefined + if (reactionKinds.includes(e.kind)) return undefined + + for (const v of getIdAndAddress(e)) { + seen.add(v) + } + + return e + }) + .filter(identity) + } + const loadMore = async () => { - limit += 10 + buffer = uniqBy(e => e.id, sortEventsDesc(buffer)) + events = [...events, ...getNewEvents()] - if ($loader.notes.length < limit) { - await loader.loadMore(20) + if (useWindowing && buffer.length < 50) { + ctrl.load(50) } } - let element, loader - let limit = 0 + let element + let exhausted = false + let useWindowing = true + let ctrl: FeedController + let events: TrustedEvent[] = [] + let buffer: TrustedEvent[] = [] + let filters: Filter[] = [neverFilter] reload() onMount(() => { - const scroller = createScroller(loadMore, {element}) + const scroller = createScroller(loadMore, { + element, + delay: 300, + threshold: 3000, + }) return () => { - loader.stop() scroller.stop() } }) @@ -83,13 +221,9 @@ {/if} - {#each $loader.notes.slice(0, limit) as note, i (note.id)} + {#each events as note, i (note.id)}
- +
{#if i > 20 && parseInt(hash(note.id)) % 100 === 0 && $promptDismissed < now() - seconds(7, "day")} @@ -110,7 +244,7 @@
{#if !hideSpinner} - {#if $loader.done} + {#if exhausted}
That's all! diff --git a/src/app/shared/Note.svelte b/src/app/shared/Note.svelte index 556820296..efaddcdfe 100644 --- a/src/app/shared/Note.svelte +++ b/src/app/shared/Note.svelte @@ -45,6 +45,7 @@ export let note export let relays = [] export let filters = null + export let reposts = new Map() export let depth = 0 export let anchor = null export let topLevel = false @@ -211,7 +212,7 @@ {@const showReply = reply && !ancestors.replies.includes(anchor) && showParent} {@const showRoot = root && !ancestors.roots.includes(anchor) && root !== reply && showParent}
- +
{#if !showParent && !topLevel} diff --git a/src/app/shared/NoteMeta.svelte b/src/app/shared/NoteMeta.svelte index 0fc145f4c..28612bf93 100644 --- a/src/app/shared/NoteMeta.svelte +++ b/src/app/shared/NoteMeta.svelte @@ -1,12 +1,12 @@ {#if repostPubkeys.length > 0} diff --git a/src/app/util/feeds.ts b/src/app/util/feeds.ts deleted file mode 100644 index 86a25e5d6..000000000 --- a/src/app/util/feeds.ts +++ /dev/null @@ -1,322 +0,0 @@ -import {partition, prop, uniqBy} from "ramda" -import {batch, tryFunc, seconds} from "hurdak" -import {get, writable, derived} from "svelte/store" -import {ctx, uniq, inc, pushToMapKey, now, chunk} from "@welshman/lib" -import type {TrustedEvent} from "@welshman/util" -import { - Tags, - guessFilterDelta, - getIdOrAddress, - getIdAndAddress, - getIdFilters, - DIRECT_MESSAGE, - REACTION, - LIVE_CHAT_MESSAGE, - getAncestorTagValues, -} from "@welshman/util" -import {Tracker} from "@welshman/net" -import type {Feed, RequestItem} from "@welshman/feeds" -import {FeedLoader as CoreFeedLoader, isIntersectionFeed, isRelayFeed} from "@welshman/feeds" -import {repository, tracker, getFilterSelections} from "@welshman/app" -import {noteKinds, isLike, reactionKinds, repostKinds} from "src/util/nostr" -import type {DisplayEvent} from "src/engine" -import { - feedLoader as baseFeedLoader, - sortEventsDesc, - unwrapRepost, - isEventMuted, - load, -} from "src/engine" - -export type FeedOpts = { - feed?: Feed - forcePlatform?: boolean - shouldHideReplies?: boolean - // Deprecated - anchor?: string -} - -const prepFilters = (filters, opts: FeedOpts) => - filters?.map(filter => ({kinds: noteKinds, ...filter})) || [] - -function* getRequestItems({relays, filters}: RequestItem, opts: FeedOpts) { - filters = prepFilters(filters, opts) - - // Use relays specified in feeds - if (relays?.length > 0) { - yield {filters, relays} - } else { - yield* getFilterSelections(filters) - } -} - -// Use a custom feed loader so we can intercept the filters and infer relays -const createFeedLoader = (opts: FeedOpts, signal) => - new CoreFeedLoader({ - ...baseFeedLoader.options, - request: async ({relays, filters, onEvent}) => { - const tracker = new Tracker() - const requestItems = Array.from(getRequestItems({relays, filters}, opts)) - - await Promise.all( - requestItems.map(item => - load({ - ...item, - onEvent, - tracker, - signal, - skipCache: true, - forcePlatform: opts.forcePlatform && relays?.length === 0, - }), - ), - ) - - // Wait until after we've queried the network to access our local cache. This results in less - // snappy response times, but is necessary to prevent stale stuff that the user has already seen - // from showing up at the top of the feed - if (relays.length === 0) { - for (const event of repository.query(prepFilters(filters, opts))) { - onEvent(event) - } - } - }, - }) - -export const createFeed = (opts: FeedOpts) => { - const done = writable(false) - const notes = writable([]) - const store = derived([done, notes], ([$done, $notes]) => ({done: $done, notes: $notes})) - - const buffer: TrustedEvent[] = [] - const parents = new Map() - const reposts = new Map() - const $isEventMuted = isEventMuted.get() - const controller = new AbortController() - const welshman = createFeedLoader(opts, controller.signal) - const useWindowing = - !isRelayFeed(opts.feed) && - !(isIntersectionFeed(opts.feed) && opts.feed.length === 2 && isRelayFeed(opts.feed[1])) - - const loaderOpts = { - useWindowing, - onEvent: batch(300, async events => { - if (controller.signal.aborted) { - return - } - - const keep = discardEvents(events) - - loadParents(keep) - - const withoutOrphans = deferOrphans(keep) - const withoutAncient = deferAncient(withoutOrphans) - - appendToFeed(withoutAncient) - }), - onExhausted: () => done.set(true), - } - - let filters, delta, loader - Promise.resolve(tryFunc(() => welshman.compiler.compile(opts.feed))).then(async reqs => { - filters = reqs?.flatMap(r => r.filters || []) - delta = filters ? guessFilterDelta(filters) : seconds(24, "hour") - - loader = await (reqs - ? welshman.getRequestsLoader(reqs, loaderOpts) - : welshman.getLoader(opts.feed, loaderOpts)) - }) - - const sortEvents = (events: TrustedEvent[]) => (useWindowing ? sortEventsDesc(events) : events) - - function deferOrphans(events: TrustedEvent[]) { - // If something has a parent id but we haven't found the parent yet, skip it until we have it. - const [ok, defer] = partition(e => { - const parentIds = Tags.fromEvent(e).parents().values().valueOf() - - return parentIds.length === 0 || parentIds.some(k => parents.has(k)) - }, events) - - if (defer.length > 0) { - setTimeout(() => { - if (!controller.signal.aborted) { - appendToFeed(defer) - } - }, 3000) - } - - return ok - } - - function deferAncient(events: TrustedEvent[]) { - if (!useWindowing) { - return events - } - - // Defer any really old notes until we're done loading from the network - const feed = get(notes) - const cutoff = feed.reduce((t, e) => Math.min(t, e.created_at), now()) - delta - const [ok, defer] = partition(e => e.created_at > cutoff, events.concat(buffer.splice(0))) - - // Add our deferred notes back to the buffer for next time - buffer.splice(0, Infinity, ...defer) - - // If nothing else has loaded after a delay, trickle a few new notes so the user has something to look at - if (defer.length > 0) { - for (let i = 0; i < defer.length; i++) { - setTimeout( - () => { - if ( - buffer.length > 0 && - !controller.signal.aborted && - get(notes).length === feed.length + i - ) { - const [event, ...events] = sortEvents(buffer) - - buffer.splice(0, Infinity, ...events) - appendToFeed([event]) - } - }, - inc(i) * 400, - ) - } - } - - return ok - } - - // Feed building - - function appendToFeed(events: TrustedEvent[]) { - notes.update($events => uniqBy(prop("id"), [...$events, ...buildFeedChunk(events)])) - } - - function buildFeedChunk(events: TrustedEvent[]) { - const seen = new Set(get(notes).map(getIdOrAddress)) - const chunkParents = [] - - // Sort first to make sure we get the latest version of replaceable events, then - // after to make sure notes replaced by their parents are in order. - return sortEvents( - uniqBy( - prop("id"), - sortEvents(events) - .map((e: TrustedEvent) => { - // If we have a repost, use its contents instead - if (repostKinds.includes(e.kind)) { - const wrappedEvent = unwrapRepost(e) - - if (wrappedEvent) { - pushToMapKey(reposts, wrappedEvent.id, e) - tracker.copy(e.id, wrappedEvent.id) - - e = wrappedEvent - } - } - - // If we have a parent, show that instead, with replies grouped underneath - while (true) { - const parentIds = Tags.fromEvent(e).parents().values().valueOf() - - if (parentIds.length === 0) { - break - } - - const parentId = parentIds.find(id => parents.get(id)) - - if (!parentId) { - break - } - - e = parents.get(parentId) - } - - return e - }) - .concat(chunkParents) - // If we've seen this note or its parent, don't add it again - .filter(e => { - if (seen.has(getIdOrAddress(e))) return false - if (repostKinds.includes(e.kind)) return false - if (reactionKinds.includes(e.kind)) return false - if (e.kind === LIVE_CHAT_MESSAGE && !Tags.fromEvent(e).parents().exists()) return false - - seen.add(getIdOrAddress(e)) - - return true - }) - .map((e: DisplayEvent) => { - e.reposts = getIdAndAddress(e).flatMap(k => reposts.get(k) || []) - - return e - }), - ), - ) - } - - function loadParents(events: TrustedEvent[]) { - // Add notes to parents too since they might match - for (const e of events) { - for (const k of getIdAndAddress(e)) { - parents.set(k, e) - } - } - - const notesWithParent = events.filter(e => { - if (repostKinds.includes(e.kind)) { - return false - } - - if ($isEventMuted(e)) { - return false - } - - const ids = Tags.fromEvent(e).parents().values().valueOf() - - if (ids.length === 0 || ids.some(k => parents.has(k))) { - return false - } - - return true - }) - - for (const events of chunk(10, notesWithParent)) { - const scenario = ctx.app.router.merge(events.map(e => ctx.app.router.EventParents(e))) - - load({ - signal: controller.signal, - relays: scenario.getUrls(), - filters: getIdFilters(uniq(events.flatMap(e => getAncestorTagValues(e.tags).replies))), - onEvent: batch(100, async events => { - if (controller.signal.aborted) { - return - } - - for (const e of discardEvents(events)) { - for (const k of getIdAndAddress(e)) { - parents.set(k, e) - } - } - }), - }) - } - } - - function discardEvents(events) { - return events.filter(e => { - if (repository.isDeleted(e)) return false - if (e.kind === REACTION && !isLike(e)) return false - if ([4, DIRECT_MESSAGE].includes(e.kind)) return false - if (opts.shouldHideReplies && Tags.fromEvent(e).parent()) return false - if ($isEventMuted(e, true)) return false - - return true - }) - } - - return { - getFilters: () => filters, - stop: () => controller.abort(), - subscribe: f => store.subscribe(f), - loadMore: (limit: number) => loader?.(useWindowing ? limit : 1000), - } -} diff --git a/src/app/util/index.ts b/src/app/util/index.ts index aa5600e4c..476c868cd 100644 --- a/src/app/util/index.ts +++ b/src/app/util/index.ts @@ -1,2 +1 @@ -export * from "src/app/util/feeds" export * from "src/app/util/router" diff --git a/src/engine/model.ts b/src/engine/model.ts index 06da2da1b..5a19ec3bf 100644 --- a/src/engine/model.ts +++ b/src/engine/model.ts @@ -2,11 +2,6 @@ import type {Session} from "@welshman/app" import type {Publish} from "@welshman/net" import type {TrustedEvent, Zapper as WelshmanZapper} from "@welshman/util" -export type DisplayEvent = TrustedEvent & { - replies?: DisplayEvent[] - reposts?: TrustedEvent[] -} - export type Zapper = WelshmanZapper & { lnurl: string pubkey: string diff --git a/src/engine/requests.ts b/src/engine/requests.ts index d44ea2391..271e72840 100644 --- a/src/engine/requests.ts +++ b/src/engine/requests.ts @@ -1,27 +1,12 @@ import {debounce} from "throttle-debounce" import {get, writable, derived} from "svelte/store" -import {noop, sleep, switcherFn} from "hurdak" -import type {LoadOpts} from "@welshman/feeds" -import {FeedLoader, Scope} from "@welshman/feeds" -import { - ctx, - assoc, - always, - chunk, - nthEq, - nth, - now, - max, - first, - int, - HOUR, - WEEK, - sortBy, -} from "@welshman/lib" +import {noop, sleep} from "hurdak" +import type {RequestOpts, Feed} from "@welshman/feeds" +import {FeedController} from "@welshman/feeds" +import {ctx, assoc, always, chunk, max, first, int, HOUR, WEEK, sortBy} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" import { getIdFilters, - createEvent, WRAP, EPOCH, LABEL, @@ -36,23 +21,21 @@ import { DEPRECATED_DIRECT_MESSAGE, FEEDS, } from "@welshman/util" +import {Tracker} from "@welshman/net" import {deriveEvents} from "@welshman/store" -import {makeDvmRequest} from "@welshman/dvm" import { pubkey, repository, - signer, loadProfile, loadFollows, loadMutes, getFilterSelections, - getFollowers, getFollows, pull, hasNegentropy, - wotGraph, - maxWot, - getNetwork, + requestDVM, + getPubkeysForScope, + getPubkeysForWOTRange, } from "@welshman/app" import type {AppSyncOpts} from "@welshman/app" import {noteKinds, reactionKinds} from "src/util/nostr" @@ -94,28 +77,22 @@ export const pullConservatively = ({relays, filters}: AppSyncOpts) => { return Promise.all(promises) } -export const loadAll = (feed, opts: LoadOpts = {}) => { +export const loadAll = (feed, {onEvent}: {onEvent: (e: TrustedEvent) => void}) => { const loading = writable(true) - const stop = () => loading.set(false) + const onExhausted = () => loading.set(false) const promise = new Promise(async resolve => { - const load = await feedLoader.getLoader(feed, { - onEvent: opts.onEvent, - onExhausted: () => { - opts.onExhausted?.() - stop() - }, - }) + const ctrl = createFeedController({feed, onEvent, onExhausted}) while (get(loading)) { - await load(100) + await ctrl.load(100) } resolve() }) - return {promise, loading, stop} + return {promise, loading, stop: onExhausted} } export const loadEvent = async (idOrAddress: string, request: Partial = {}) => @@ -198,61 +175,51 @@ export const loadPubkeys = async (pubkeys: string[]) => { // Feeds -export const feedLoader = new FeedLoader({ - request: async ({relays, filters, onEvent}) => { +export type FeedRequestHandlerOptions = {forcePlatform: boolean} + +export const makeFeedRequestHandler = + ({forcePlatform}: FeedRequestHandlerOptions) => + async ({relays, filters, onEvent}: RequestOpts) => { + const tracker = new Tracker() + const loadOptions = {onEvent, tracker, forcePlatform, skipCache: true} + if (relays?.length > 0) { - await load({filters, relays, onEvent, skipCache: true, forcePlatform: false}) + await load({...loadOptions, filters, relays}) } else { await Promise.all( - getFilterSelections(filters).map(({relays, filters}) => load({filters, relays, onEvent})), + getFilterSelections(filters).map(({relays, filters}) => + load({...loadOptions, relays, filters}), + ), ) - } - }, - requestDVM: async ({kind, onEvent, tags = [], ...request}) => { - tags = [...tags, ["expiration", String(now() + 5)]] - - const req = makeDvmRequest({ - event: await signer.get().sign(createEvent(kind, {tags})), - relays: - request.relays?.length > 0 - ? ctx.app.router.FromRelays(request.relays).getUrls() - : ctx.app.router.ForPubkeys(tags.filter(nthEq(0, "p")).map(nth(1))).getUrls(), - }) - - await new Promise(resolve => { - req.emitter.on("result", (url, event) => { + + // Wait until after we've queried the network to access our local cache. This results in less + // snappy response times, but is necessary to prevent stale stuff that the user has already seen + // from showing up at the top of the feed + for (const event of repository.query(filters)) { onEvent(event) - resolve() - }) - }) - }, - getPubkeysForScope: (scope: string) => { - const $pubkey = pubkey.get() - - const pubkeys = switcherFn(scope, { - [Scope.Self]: () => ($pubkey ? [$pubkey] : []), - [Scope.Follows]: () => getFollows($pubkey), - [Scope.Network]: () => getNetwork($pubkey), - [Scope.Followers]: () => getFollowers($pubkey), - default: always([]), - }) - - return pubkeys.length === 0 ? env.DEFAULT_FOLLOWS : pubkeys - }, - getPubkeysForWOTRange: (min, max) => { - const pubkeys = [] - const thresholdMin = maxWot.get() * min - const thresholdMax = maxWot.get() * max - - for (const [tpk, score] of wotGraph.get().entries()) { - if (score >= thresholdMin && score <= thresholdMax) { - pubkeys.push(tpk) } } + } - return pubkeys - }, -}) +export type FeedControllerOptions = { + feed: Feed + onEvent: (event: TrustedEvent) => void + onExhausted: () => void + forcePlatform?: boolean + useWindowing?: boolean +} + +export const createFeedController = ({forcePlatform = true, ...options}: FeedControllerOptions) => { + const request = makeFeedRequestHandler({forcePlatform}) + + return new FeedController({ + request, + requestDVM, + getPubkeysForScope, + getPubkeysForWOTRange, + ...options, + }) +} // Notifications From 95b0602c80b7d0e2eb412c2705e7682c19f67ac8 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 7 Nov 2024 15:23:20 -0800 Subject: [PATCH 3/4] Remove groupHints --- src/engine/state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/state.ts b/src/engine/state.ts index 228dc3406..e39fb9bb8 100644 --- a/src/engine/state.ts +++ b/src/engine/state.ts @@ -148,7 +148,6 @@ export const hasNip44 = derived(signer, $signer => Boolean($signer?.nip44)) // Base state export const anonymous = withGetter(writable({follows: [], relays: []})) -export const groupHints = withGetter(writable>({})) export const publishes = withGetter(writable>({})) export const projections = new Worker({ From 02b5bb7626757537ffe8309cb4e164b3fe91d804 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Fri, 8 Nov 2024 09:19:36 -0800 Subject: [PATCH 4/4] Fix feeds when not logged in --- .env | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/app/MenuMobile.svelte | 6 +----- src/app/shared/Feed.svelte | 7 ++++--- src/app/views/Feeds.svelte | 12 ++++++++---- src/app/views/PersonDetail.svelte | 2 +- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.env b/.env index 26bd9adb5..7aa242957 100644 --- a/.env +++ b/.env @@ -4,7 +4,7 @@ VITE_DVM_RELAYS=wss://relay.damus.io,wss://offchain.pub,wss://relay.f7z.io,wss:/ VITE_SEARCH_RELAYS=wss://relay.nostr.band,wss://nostr.wine,wss://search.nos.today VITE_DEFAULT_RELAYS=wss://relay.damus.io,wss://nos.lol VITE_INDEXER_RELAYS=wss://relay.nostr.band,wss://purplepag.es,wss://relay.damus.io -VITE_DEFAULT_FOLLOWS=fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3,f4db5270bd991b17bea1e6d035f45dee392919c29474bbac10342d223c74e0d0,180a6d42c7d64f8c3958d9d10dd5a4117eaaacea8e7f980781e9a53136cf5693,7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805,676ffea2ec31426a906d7795d7ebae2ba5e61f0b9fa815995b4a299dd085d510,4d5ce768123563bc583697db5e84841fb528f7b708d966f2e546286ce3c72077,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9,85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204,74ffc51cc30150cf79b6cb316d3a15cf332ab29a38fec9eb484ab1551d6d1856,93518f91dfa51d8acf39217cdcd3d2ccd178433cb9e72368544aacd7412cb50c,f728d9e6e7048358e70930f5ca64b097770d989ccd86854fe618eda9c8a38106,91c9a5e1a9744114c6fe2d61ae4de82629eaaa0fb52f48288093c7e7e036f832,83e818dfbeccea56b0f551576b3fd39a7a50e1d8159343500368fa085ccd964b,090254801a7e8e5085b02e711622f0dfa1a85503493af246aa42af08f5e4d2df,472f440f29ef996e92a186b8d320ff180c855903882e59d50de1b8bd5669301e,7b3f7803750746f455413a221f80965eecb69ef308f2ead1da89cc2c8912e968,330fb1431ff9d8c250706bbcdc016d5495a3f744e047a408173e92ae7ee42dac,c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0,a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110,f9acb0b034c4c1177e985f14639f317ef0fedee7657c060b146ee790024317ec,6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93,e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411,6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32,e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af,38dbb9b07d93861d40620ad62d44b1a8e8785df0997eeb4454f12d217048cd5c,064de2497ce621aee2a5b4b926a08b1ca01bce9da85b0c714e883e119375140c,aa5e6ccfc7cb7c3431d12b0ea4b83e5b35427602522080a6a8618950527f811b,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,676ffea2ec31426a906d7795d7ebae2ba5e61f0b9fa815995b4a299dd085d510,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,ffca54e078fd9884a745d70eb0159c0b8a9e7d383a4906a7484b83ebf6101dd5,9a4acdeb978565e27490dca65c83e9f65745eaec1d9a0405a52d198c1489913b,e5177ebf513530c2d0924083b64b7eadd7fb85efcc3e4dfb55c73a924c901ca7,5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,26bd32c67232bdf16d05e763ec67d883015eb99fd1269025224c20c6cfdb0158,d307643547703537dfdef811c3dea96f1f9e84c8249e200353425924a9908cf8,604e96e099936a104883958b040b47672e0f048c98ac793f37ffe4c720279eb2,90b9bec74789688e515125596ab6350bfe646176ac75742275063922c5fea010,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,66bd8fed3590f2299ef0128f58d67879289e6a99a660e83ead94feab7606fd17,eeb11961b25442b16389fe6c7ebea9adf0ac36dd596816ea7119e521b8821b9e,61066504617ee79387021e18c89fb79d1ddbc3e7bff19cf2298f40466f8715e9,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,a9434ee165ed01b286becfc2771ef1705d3537d051b387288898cc00d5c885be,ee6ea13ab9fe5c4a68eaf9b1a34fe014a66b40117c50ee2a614f4cda959b6e74,7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805,1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110,e56e7b4326618f3d626c0e398f5082c3b16732e469e0a048b7ddb544c2be294a,011c1b374c12fbd3633e98957d3c46bed67983abecef50706c73a77c171d0d2c,b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc,b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,0fecf65daa26faf3f668e8143325a4c199a040b6345ed40a08614d7dd85b1823,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240,33bd77e5394520747faae1394a4af5fa47f404389676375b6dc7be865ed81452,21335073401a310cc9179fe3a77e9666710cfdf630dfd840f972c183a244b1ad,36732cc35fe56185af1b11160a393d6c73a1fe41ddf1184c10394c28ca5d627b,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245,63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,e8ed3798c6ffebffa08501ac39e271662bfd160f688f94c45d692d8767dd345a,c5fb6ecc876e0458e3eca9918e370cbcd376901c58460512fe537a46e58c38bb,40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451,a3eb29554bd27fca7f53f66272e4bb59d066f2f31708cf341540cb4729fbd841,460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c,b154080cb49639bb079a6a53c1d98e7130eeab3c61aa95dd9e38f9e400027cc7,0000005f87f64341c212cc93d6c266c03ae752c02660e78a6da1424f7b05c470,d61f3bc5b3eb4400efdae6169a5c17cabf3246b514361de939ce4a1a0da6ef4a,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,7adb520c3ac7cb6dc8253508df0ce1d975da49fefda9b5c956744a049d230ace,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,17717ad4d20e2a425cda0a2195624a0a4a73c4f6975f16b1593fc87fa46f2d58,af9d70407464247d19fd243cf1bee81e6df1e639217dc66366bf37aa42d05d35,ddf03aca85ade039e6742d5bef3df352df199d0d31e22b9858e7eda85cb3bbbe,d36e8083fa7b36daee646cb8b3f99feaa3d89e5a396508741f003e21ac0b6bec,7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3d842afecd5e293f28b6627933704a3fb8ce153aa91d790ab11f6a752d44a42d,79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6,17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4,ff27d01cb1e56fb58580306c7ba76bb037bf211c5b573c56e4e70ca858755af0,8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c,27797bd4e5ee52db0a197668c92b9a3e7e237e1f9fa73a10c38d731c294cfc9a,7cc328a08ddb2afdf9f9be77beff4c83489ff979721827d628a542f32a247c0e,c35ff8c340449f0d68af1aec4844bb44a9c0b8c1dd4f4d4efbc65e12039a348a,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,dace63b00c42e6e017d00dd190a9328386002ff597b841eb5ef91de4f1ce8491,76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075 +VITE_DEFAULT_FOLLOWS=fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3,f4db5270bd991b17bea1e6d035f45dee392919c29474bbac10342d223c74e0d0,180a6d42c7d64f8c3958d9d10dd5a4117eaaacea8e7f980781e9a53136cf5693,7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805,676ffea2ec31426a906d7795d7ebae2ba5e61f0b9fa815995b4a299dd085d510,4d5ce768123563bc583697db5e84841fb528f7b708d966f2e546286ce3c72077,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9,85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204,74ffc51cc30150cf79b6cb316d3a15cf332ab29a38fec9eb484ab1551d6d1856,93518f91dfa51d8acf39217cdcd3d2ccd178433cb9e72368544aacd7412cb50c,f728d9e6e7048358e70930f5ca64b097770d989ccd86854fe618eda9c8a38106,91c9a5e1a9744114c6fe2d61ae4de82629eaaa0fb52f48288093c7e7e036f832,83e818dfbeccea56b0f551576b3fd39a7a50e1d8159343500368fa085ccd964b,090254801a7e8e5085b02e711622f0dfa1a85503493af246aa42af08f5e4d2df,472f440f29ef996e92a186b8d320ff180c855903882e59d50de1b8bd5669301e,7b3f7803750746f455413a221f80965eecb69ef308f2ead1da89cc2c8912e968,330fb1431ff9d8c250706bbcdc016d5495a3f744e047a408173e92ae7ee42dac,c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0,a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110,f9acb0b034c4c1177e985f14639f317ef0fedee7657c060b146ee790024317ec,6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93,e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411,6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32,e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af,38dbb9b07d93861d40620ad62d44b1a8e8785df0997eeb4454f12d217048cd5c,064de2497ce621aee2a5b4b926a08b1ca01bce9da85b0c714e883e119375140c,aa5e6ccfc7cb7c3431d12b0ea4b83e5b35427602522080a6a8618950527f811b,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,676ffea2ec31426a906d7795d7ebae2ba5e61f0b9fa815995b4a299dd085d510,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,9a4acdeb978565e27490dca65c83e9f65745eaec1d9a0405a52d198c1489913b,e5177ebf513530c2d0924083b64b7eadd7fb85efcc3e4dfb55c73a924c901ca7,5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,26bd32c67232bdf16d05e763ec67d883015eb99fd1269025224c20c6cfdb0158,d307643547703537dfdef811c3dea96f1f9e84c8249e200353425924a9908cf8,604e96e099936a104883958b040b47672e0f048c98ac793f37ffe4c720279eb2,90b9bec74789688e515125596ab6350bfe646176ac75742275063922c5fea010,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,66bd8fed3590f2299ef0128f58d67879289e6a99a660e83ead94feab7606fd17,eeb11961b25442b16389fe6c7ebea9adf0ac36dd596816ea7119e521b8821b9e,61066504617ee79387021e18c89fb79d1ddbc3e7bff19cf2298f40466f8715e9,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,a9434ee165ed01b286becfc2771ef1705d3537d051b387288898cc00d5c885be,ee6ea13ab9fe5c4a68eaf9b1a34fe014a66b40117c50ee2a614f4cda959b6e74,7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805,1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450,958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c,a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110,e56e7b4326618f3d626c0e398f5082c3b16732e469e0a048b7ddb544c2be294a,011c1b374c12fbd3633e98957d3c46bed67983abecef50706c73a77c171d0d2c,b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc,b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e,5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78,baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,0fecf65daa26faf3f668e8143325a4c199a040b6345ed40a08614d7dd85b1823,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,f783ba3b12b91e375aba6594015b90bd95f7e132b03cc8c4c52ce0a7c36aab52,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240,33bd77e5394520747faae1394a4af5fa47f404389676375b6dc7be865ed81452,21335073401a310cc9179fe3a77e9666710cfdf630dfd840f972c183a244b1ad,36732cc35fe56185af1b11160a393d6c73a1fe41ddf1184c10394c28ca5d627b,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245,63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,e8ed3798c6ffebffa08501ac39e271662bfd160f688f94c45d692d8767dd345a,c5fb6ecc876e0458e3eca9918e370cbcd376901c58460512fe537a46e58c38bb,40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451,a3eb29554bd27fca7f53f66272e4bb59d066f2f31708cf341540cb4729fbd841,460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c,0000005f87f64341c212cc93d6c266c03ae752c02660e78a6da1424f7b05c470,d61f3bc5b3eb4400efdae6169a5c17cabf3246b514361de939ce4a1a0da6ef4a,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,7adb520c3ac7cb6dc8253508df0ce1d975da49fefda9b5c956744a049d230ace,3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,17717ad4d20e2a425cda0a2195624a0a4a73c4f6975f16b1593fc87fa46f2d58,af9d70407464247d19fd243cf1bee81e6df1e639217dc66366bf37aa42d05d35,ddf03aca85ade039e6742d5bef3df352df199d0d31e22b9858e7eda85cb3bbbe,d36e8083fa7b36daee646cb8b3f99feaa3d89e5a396508741f003e21ac0b6bec,7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194,cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,3d842afecd5e293f28b6627933704a3fb8ce153aa91d790ab11f6a752d44a42d,79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6,17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4,ff27d01cb1e56fb58580306c7ba76bb037bf211c5b573c56e4e70ca858755af0,8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c,27797bd4e5ee52db0a197668c92b9a3e7e237e1f9fa73a10c38d731c294cfc9a,7cc328a08ddb2afdf9f9be77beff4c83489ff979721827d628a542f32a247c0e,c35ff8c340449f0d68af1aec4844bb44a9c0b8c1dd4f4d4efbc65e12039a348a,1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411,fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb,2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884,1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef,eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f,dace63b00c42e6e017d00dd190a9328386002ff597b841eb5ef91de4f1ce8491,76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075,7a3288f5b2a382317ddcbab9c0b6e9a22a999a064dfb9b7284508a0da3fa9114,6a72db8ef3f3b9ee5ecd808ed6d0631d1e4dda5c5dadf07887104d33957eba48 VITE_ONBOARDING_LISTS="30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:3121977322800018,30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:680038570738458,30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:33178290670580934,30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:49330924355910266,30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:9358486925304412,30000:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:3151213286926533" VITE_NIP96_URLS=https://nostr.build,https://nostrcheck.me,https://sove.rent,https://void.cat VITE_IMGPROXY_URL=https://imgproxy.coracle.social diff --git a/package-lock.json b/package-lock.json index cd65443d8..abd7ecf7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,10 +27,10 @@ "@types/ramda": "^0.30.2", "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", - "@welshman/app": "~0.0.22", + "@welshman/app": "~0.0.23", "@welshman/content": "~0.0.12", "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.22", + "@welshman/feeds": "~0.0.23", "@welshman/lib": "~0.0.24", "@welshman/net": "~0.0.33", "@welshman/signer": "~0.0.11", @@ -4775,13 +4775,13 @@ } }, "node_modules/@welshman/app": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.22.tgz", - "integrity": "sha512-0qC3mXIIorpBndXSYV9w160woN05ytPWeJreXbJH+Xki7myPpgMf18dMZR/8z5iIJ3UvIG14XQNb2nOqZxmt4Q==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@welshman/app/-/app-0.0.24.tgz", + "integrity": "sha512-psBzrDwowQkcIwTmY9nauQB3QrbtH5+kOvUs1rM2Hwbtpn4I9BuyQ0VKA9cr+4qdTHfYeufr8Ndb4h3ghpX3WA==", "license": "MIT", "dependencies": { "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.22", + "@welshman/feeds": "~0.0.24", "@welshman/lib": "~0.0.24", "@welshman/net": "~0.0.33", "@welshman/signer": "~0.0.11", @@ -4816,9 +4816,9 @@ } }, "node_modules/@welshman/feeds": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.22.tgz", - "integrity": "sha512-7ysWrTXX2Eh0yvBKSIyVdX4moajHLYOlLjU/fq64oV9UPyJA77hniuJWrI9Qer9wChL+u2S6nY/7eaLprPiY/w==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@welshman/feeds/-/feeds-0.0.24.tgz", + "integrity": "sha512-7yVJH+fLcvRBumF+NVw/eqhGGV1Q361Zjooq5h6UOB3RS5RnjqtYyhneMkJeW9Xlcd/Rzt/jh8tUd1OUKadfyw==", "license": "MIT", "dependencies": { "@welshman/lib": "~0.0.24", diff --git a/package.json b/package.json index 93444bbc1..ee61c1dbe 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,10 @@ "@types/ramda": "^0.30.2", "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", - "@welshman/app": "~0.0.22", + "@welshman/app": "~0.0.23", "@welshman/content": "~0.0.12", "@welshman/dvm": "~0.0.10", - "@welshman/feeds": "~0.0.22", + "@welshman/feeds": "~0.0.23", "@welshman/lib": "~0.0.24", "@welshman/net": "~0.0.33", "@welshman/signer": "~0.0.11", diff --git a/src/app/MenuMobile.svelte b/src/app/MenuMobile.svelte index b83cc225d..bb2f791af 100644 --- a/src/app/MenuMobile.svelte +++ b/src/app/MenuMobile.svelte @@ -1,5 +1,4 @@ -{#if !$session} +{#if !$pubkey}

Don't have an account?

diff --git a/src/app/views/PersonDetail.svelte b/src/app/views/PersonDetail.svelte index 89414c4b1..c5f217d34 100644 --- a/src/app/views/PersonDetail.svelte +++ b/src/app/views/PersonDetail.svelte @@ -122,7 +122,7 @@ button class="!bg-neutral-800 dark:!bg-white" on:click={router.at("/settings/profile").open}>Edit - {:else} + {:else if $session}