diff --git a/.husky/pre-commit b/.husky/pre-commit index 524f353b..b5588ea2 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,6 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npm run lint -npm run check +npm run qa:lint +npm run qa:check + diff --git a/README.md b/README.md index 669dd01e..3a87a446 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ If you like Coracle and want to support its development, you can donate sats via - Put user detail in a modal? - ReplaceState for settings modals? - [ ] Mentions are sorta weird, usually mention self -- [ ] Alerts are not showing likes, just generally screwy. Maybe because I threadify before adding to the db? - [ ] Change network tab to list relays the user is connected to - [ ] Sync mentions box and in-reply mentions - [ ] Add petnames for channels @@ -63,11 +62,17 @@ If you like Coracle and want to support its development, you can donate sats via # Changelog +## 0.2.10 + +- [x] Fixed likes not showing up in alerts +- [x] Raised threshold for pool to 2 so we don't have such a small amount of results +- [x] Wait for profile info on login, navigate to network by default + ## 0.2.9 -- [x] Fix a bug in pool.subscribe which was causing requests to wait for all connections -- [x] Add typescript with pre-commit hook -- [x] Fix layout for chat, person pages +- [x] Fixed a bug in pool.subscribe which was causing requests to wait for all connections +- [x] Added typescript with pre-commit hook +- [x] Fixed layout for chat, person pages - [x] Parse relays for kind 3 ## 0.2.8 diff --git a/package-lock.json b/package-lock.json index c6ffb56a..3e9fd068 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 4f5b0f30..5f3bd4b6 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,10 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint src/*/** --quiet", - "check": "svelte-check --tsconfig ./tsconfig.json --threshold error" + "qa:lint": "eslint src/*/** --quiet", + "qa:check": "svelte-check --tsconfig ./tsconfig.json --threshold error", + "qa:all": "run-p qa:lint qa:check", + "watch": "find src -type f | entr -r npm run qa:all" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^1.1.0", @@ -36,6 +38,7 @@ "hurdak": "github:ConsignCloud/hurdak", "husky": "^8.0.3", "nostr-tools": "^1.2.1", + "npm-run-all": "^4.1.5", "ramda": "^0.28.0", "svelte-check": "^3.0.3", "svelte-link-preview": "^0.3.3", diff --git a/src/App.svelte b/src/App.svelte index e0355f01..bdcae9a8 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -12,9 +12,10 @@ import {displayPerson, isLike} from 'src/util/nostr' import {timedelta, now} from 'src/util/misc' import {keys, user, pool, getRelays} from 'src/agent' - import {modal, toast, settings, logUsage, alerts, messages} from "src/app" + import {modal, toast, settings, logUsage, alerts, messages, loadAppData} from "src/app" import {routes} from "src/app/ui" import Anchor from 'src/partials/Anchor.svelte' + import Content from 'src/partials/Content.svelte' import Spinner from 'src/partials/Spinner.svelte' import Modal from 'src/partials/Modal.svelte' import SignUp from "src/views/SignUp.svelte" @@ -74,9 +75,7 @@ onMount(() => { if ($user) { - alerts.load(getRelays(), $user.pubkey) - alerts.listen(getRelays(), $user.pubkey) - messages.listen(getRelays(), $user.pubkey) + loadAppData($user.pubkey) } const interval = setInterval(() => { @@ -301,6 +300,13 @@ {:else if $modal.type === 'person/settings'} + {:else if $modal.type === 'message'} + +
{$modal.message}
+ {#if $modal.spinner} + + {/if} +
{/if} {/if} diff --git a/src/agent/pool.ts b/src/agent/pool.ts index dfa45b1f..89e1c3e9 100644 --- a/src/agent/pool.ts +++ b/src/agent/pool.ts @@ -192,17 +192,15 @@ const request = (relays, filters, {threshold = 2} = {}): Promise { - const done = ( - eose.length === relays.length - || eose.filter(url => relaysWithEvents.has(url)).length >= threshold - || ( - Date.now() - now >= 1000 - && eose.length > relays.length - Math.round(relays.length / 10) - ) - || Date.now() - now >= 5000 + const allEose = eose.length === relays.length + const atThreshold = eose.filter(url => relaysWithEvents.has(url)).length >= threshold + const hardTimeout = Date.now() - now >= 5000 + const softTimeout = ( + Date.now() - now >= 1000 + && eose.length > relays.length - Math.round(relays.length / 10) ) - if (done) { + if (allEose || atThreshold || hardTimeout || softTimeout) { agg.unsub() resolve(events) } diff --git a/src/app/alerts.js b/src/app/alerts.js index fe98c6fd..9c1d32d7 100644 --- a/src/app/alerts.js +++ b/src/app/alerts.js @@ -1,9 +1,10 @@ import {get} from 'svelte/store' -import {synced, batch, now} from 'src/util/misc' -import {isAlert} from 'src/util/nostr' -import {load as _load, listen as _listen, getMuffle, db} from 'src/agent' +import {groupBy, pluck, partition, propEq} from 'ramda' +import {synced, timedelta, batch, now} from 'src/util/misc' +import {isAlert, findReplyId} from 'src/util/nostr' +import {load as _load, listen as _listen, db} from 'src/agent' import loaders from 'src/app/loaders' -import {threadify} from 'src/app' +import {annotate} from 'src/app' let listener @@ -14,21 +15,29 @@ const onChunk = async (relays, pubkey, events) => { events = events.filter(e => isAlert(e, pubkey)) if (events.length > 0) { - const context = await loaders.loadContext(relays, events, {threshold: 2}) - const notes = threadify(events, context, {muffle: getMuffle()}) + const context = await loaders.loadContext(relays, events) + const [likes, notes] = partition(propEq('kind', 7), events) + const annotatedNotes = notes.map(n => annotate(n, context)) + const likesByParent = groupBy(findReplyId, likes) + const likedNotes = context + .filter(e => likesByParent[e.id]) + .map(e => annotate({...e, likedBy: pluck('pubkey', likesByParent[e.id])}, context)) - await db.table('alerts').bulkPut(notes) + await db.table('alerts').bulkPut(annotatedNotes.concat(likedNotes)) mostRecentAlert.update($t => events.reduce((t, e) => Math.max(t, e.created_at), $t)) } } const load = async (relays, pubkey) => { - const since = get(mostRecentAlert) + // Include an offset so we don't miss alerts on one relay but not another + const since = get(mostRecentAlert) - timedelta(30, 'days') + + // Crank the threshold up since we can afford for this to be slow const events = await _load( relays, - {kinds: [1, 7], '#p': [pubkey], since, limit: 100}, - {threshold: 2} + {kinds: [1, 7], '#p': [pubkey], since, limit: 1000}, + {threshold: 10} ) onChunk(relays, pubkey, events) diff --git a/src/app/index.ts b/src/app/index.ts index b07798de..55707c18 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -14,6 +14,15 @@ import loaders from 'src/app/loaders' export {toast, modal, settings, alerts, messages, logUsage} +export const loadAppData = pubkey => { + return Promise.all([ + loaders.loadNetwork(getRelays(), pubkey), + alerts.load(getRelays(), pubkey), + alerts.listen(getRelays(), pubkey), + messages.listen(getRelays(), pubkey), + ]) +} + export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: string}, usingExtension = false) => { if (privkey) { keys.setPrivateKey(privkey) @@ -21,16 +30,18 @@ export const login = async ({privkey, pubkey}: {privkey?: string, pubkey?: strin keys.setPublicKey(pubkey) } + modal.set({type: 'message', message: "Loading your profile data...", spinner: true}) + // Load network and start listening, but don't wait for it - loaders.loadNetwork(getRelays(), pubkey), - alerts.load(getRelays(), pubkey), - alerts.listen(getRelays(), pubkey), - messages.listen(getRelays(), pubkey) + loadAppData(pubkey) + + // Load our user so we can populate network and show profile info + await loaders.loadPeople(getRelays(), [pubkey]) // Not ideal, but the network tab depends on the user's social network being // loaded, so put them on global when they first log in so we're not slowing // down users' first run experience too much - navigate('/notes/global') + navigate('/notes/network') } export const addRelay = async relay => { @@ -46,12 +57,8 @@ export const addRelay = async relay => { // Publish to the new set of relays await cmd.setRelays(relays, relays) - await Promise.all([ - loaders.loadNetwork(relays, person.pubkey), - alerts.load(relays, person.pubkey), - alerts.listen(relays, person.pubkey), - messages.listen(getRelays(), person.pubkey) - ]) + // Reload alerts, messages, etc + await loadAppData(person.pubkey) } } @@ -126,6 +133,7 @@ export const annotate = (note, context) => { export const threadify = (events, context, {muffle = [], showReplies = true} = {}) => { const contextById = createMap('id', events.concat(context)) + // Show parents when possible. For reactions, if there's no parent, // throw it away. Sort by created date descending const notes = sortBy( diff --git a/src/app/loaders.ts b/src/app/loaders.ts index 7a98dcbb..bbcc811b 100644 --- a/src/app/loaders.ts +++ b/src/app/loaders.ts @@ -2,8 +2,7 @@ import {uniqBy, prop, uniq, flatten, pluck, identity} from 'ramda' import {ensurePlural, createMap, chunk} from 'hurdak/lib/hurdak' import {findReply, personKinds, Tags} from 'src/util/nostr' import {now, timedelta} from 'src/util/misc' -import {load, getPerson} from 'src/agent' -import defaults from 'src/agent/defaults' +import {load, getPerson, getFollows} from 'src/agent' const getStalePubkeys = pubkeys => { // If we're not reloading, only get pubkeys we don't already know about @@ -28,17 +27,7 @@ const loadPeople = (relays, pubkeys, {kinds = personKinds, force = false, ...opt } const loadNetwork = async (relays, pubkey) => { - // Get this user's profile to start with. This may update what relays - // are available, so don't assign relays to a variable here. - const events = pubkey ? await loadPeople(relays, [pubkey], {force: true}) : [] - let petnames = Tags.from(events.filter(e => e.kind === 3)).type("p").all() - - // Default to some cool guys we know - if (petnames.length === 0) { - petnames = defaults.petnames - } - - const tags = Tags.wrap(petnames) + const tags = Tags.wrap(getFollows(pubkey)) // Use nip-2 recommended relays to load our user's second-order follows await loadPeople(tags.relays(), tags.values().all()) diff --git a/src/partials/Compose.svelte b/src/partials/Compose.svelte index 3c0a28be..a7a78f0b 100644 --- a/src/partials/Compose.svelte +++ b/src/partials/Compose.svelte @@ -178,7 +178,7 @@ {#if suggestions.length > 0} -
+
{#each suggestions as person, i (person.pubkey)} {#if isOpen} {/if} diff --git a/src/partials/Spinner.svelte b/src/partials/Spinner.svelte index 8c2017b9..c3386213 100644 --- a/src/partials/Spinner.svelte +++ b/src/partials/Spinner.svelte @@ -1,9 +1,11 @@ -
+
diff --git a/src/routes/Alerts.svelte b/src/routes/Alerts.svelte index bc0b6fe4..618daa1a 100644 --- a/src/routes/Alerts.svelte +++ b/src/routes/Alerts.svelte @@ -3,14 +3,14 @@ import {onMount} from 'svelte' import {fly} from 'svelte/transition' import {now, createScroller} from 'src/util/misc' - import {user, db} from 'src/agent' + import {db} from 'src/agent' import {alerts} from 'src/app' import Note from 'src/partials/Note.svelte' import Content from 'src/partials/Content.svelte' import Like from 'src/partials/Like.svelte' let limit = 0 - let annotatedNotes = [] + let notes = [] onMount(async () => { alerts.lastCheckedAlerts.set(now()) @@ -19,37 +19,24 @@ limit += 10 const events = await db.table('alerts').toArray() - const notes = events.filter(e => e.kind === 1) - const likes = events.filter(e => e.kind === 7) - // Combine likes of a single note. Remove grandchild likes - const likesById = {} - for (const like of likes.filter(e => e.parent?.pubkey === $user.pubkey)) { - if (!likesById[like.parent.id]) { - likesById[like.parent.id] = {...like.parent, people: []} - } - - likesById[like.parent.id].people.push(like.person) - } - - annotatedNotes = sortBy( - e => -e.created_at, - notes - .filter(e => e && e.pubkey !== $user.pubkey) - .concat(Object.values(likesById)) - ).slice(0, limit) + notes = sortBy(e => -e.created_at, events).slice(0, limit) }) }) - {#each annotatedNotes as e (e.id)} + {#each notes as e (e.id)}
- {#if e.people} + {#if e.likedBy} {:else} {/if}
+ {:else} + + No alerts found - check back later! + {/each}