Fix alerts
This commit is contained in:
parent
30270d331c
commit
fd3f8ec4f6
27
README.md
27
README.md
|
@ -1,11 +1,13 @@
|
||||||
# What is this?
|
# What is this?
|
||||||
|
|
||||||
Coracle is a web client for the Nostr protocol. While Nostr is useful for many things, Coracle focuses on providing a high-quality user experience. Check it out at [coracle.social](https://coracle.social).
|
Coracle is a web client for the Nostr protocol. While Nostr is useful for many things, Coracle focuses on providing a high-quality social media experience. Check it out at [coracle.social](https://coracle.social).
|
||||||
|
|
||||||
[Dufflepud](https://github.com/staab/dufflepud) is a companion server which you can self-host. It helps Coracle with things like link previews and image uploads.
|
[Dufflepud](https://github.com/staab/dufflepud) is a companion server which you can self-host. It helps Coracle with things like link previews and image uploads.
|
||||||
|
|
||||||
Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edges.
|
Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edges.
|
||||||
|
|
||||||
|
If you like Coracle and want to support its development, you can donate sats via [Geyser](https://geyser.fund/project/coracle).
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- [x] Chat
|
- [x] Chat
|
||||||
|
@ -24,7 +26,6 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||||
|
|
||||||
# Bugs
|
# Bugs
|
||||||
|
|
||||||
- [ ] Use https://nostr.watch/relays.json to populate relays
|
|
||||||
- [ ] Add alerts for replies to posts the user liked
|
- [ ] Add alerts for replies to posts the user liked
|
||||||
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
||||||
- [ ] Stack views so scroll position isn't lost on navigation
|
- [ ] Stack views so scroll position isn't lost on navigation
|
||||||
|
@ -32,23 +33,9 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||||
- [ ] Wait for 60% or so of relays to eose to balance completeness with speed
|
- [ ] Wait for 60% or so of relays to eose to balance completeness with speed
|
||||||
- [ ] Add a CSP, check for XSS in image urls
|
- [ ] Add a CSP, check for XSS in image urls
|
||||||
|
|
||||||
# Current update
|
# Changelog
|
||||||
|
|
||||||
- [ ] Write blog post
|
## 0.2.0
|
||||||
- [x] Sync user
|
|
||||||
- [x] Based on petnames, sync network to 2 or 3 degrees of separation
|
|
||||||
- When a user is added/removed, sync them and add to or remove from network
|
|
||||||
- [x] Add cursor object to handle since/until/last sync
|
|
||||||
- [x] Separate fetching and loading from the db
|
|
||||||
- Each route should have a fetcher and loader.
|
|
||||||
- The fetcher should keep track of the range of notes it has already gotten
|
|
||||||
- Separate helper functions into loaders and fetchers
|
|
||||||
- [x] Main fetch requests:
|
|
||||||
- Fetch feed by name, since last sync
|
|
||||||
- Fetch person, including feed
|
|
||||||
- Fetch note, including context
|
|
||||||
- This is based on detail pages. Each request should check local db and fall back to network, all within an await.
|
|
||||||
|
|
||||||
# Problems to solve
|
- [x] Completely re-worked data synchronization layer, moving from naive just-in-time requests to background listeners and a local copy stored in dexie. Events and tags, but not people are deleted from the database on logout, and old events are periodically purged.
|
||||||
|
- [x]
|
||||||
- [ ] How will newcomers get followed?
|
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
import {Router, Route, links, navigate} from "svelte-routing"
|
import {Router, Route, links, navigate} from "svelte-routing"
|
||||||
import {globalHistory} from "svelte-routing/src/history"
|
import {globalHistory} from "svelte-routing/src/history"
|
||||||
import {hasParent} from 'src/util/html'
|
import {hasParent} from 'src/util/html'
|
||||||
import {timedelta, now} from 'src/util/misc'
|
import {timedelta, getLastSync, now} from 'src/util/misc'
|
||||||
import {store as toast} from "src/state/toast"
|
import {store as toast} from "src/state/toast"
|
||||||
import {modal, alerts, settings} from "src/state/app"
|
import {modal, settings, alerts} from "src/state/app"
|
||||||
import relay, {user, connections} from 'src/relay'
|
import relay, {user, connections} from 'src/relay'
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import NoteDetail from "src/views/NoteDetail.svelte"
|
import NoteDetail from "src/views/NoteDetail.svelte"
|
||||||
|
@ -43,31 +43,24 @@
|
||||||
let menuIcon
|
let menuIcon
|
||||||
let scrollY
|
let scrollY
|
||||||
let suspendedSubs = []
|
let suspendedSubs = []
|
||||||
let mostRecentAlert = relay.lq(async () => {
|
let mostRecentAlert = $alerts.since
|
||||||
const [e] = await relay.filterAlerts($user, 1)
|
|
||||||
|
|
||||||
return e?.created_at
|
const logout = async () => {
|
||||||
})
|
const $connections = get(connections)
|
||||||
|
const $settings = get(settings)
|
||||||
|
|
||||||
const logout = () => {
|
localStorage.clear()
|
||||||
// Give any animations a moment to finish
|
|
||||||
setTimeout(() => {
|
|
||||||
const $connections = get(connections)
|
|
||||||
const $settings = get(settings)
|
|
||||||
|
|
||||||
localStorage.clear()
|
// Keep relays around
|
||||||
|
await relay.db.events.clear()
|
||||||
|
await relay.db.tags.clear()
|
||||||
|
|
||||||
// Keep relays around
|
// Remember the user's relay selection and settings
|
||||||
relay.db.events.clear()
|
connections.set($connections)
|
||||||
relay.db.tags.clear()
|
settings.set($settings)
|
||||||
|
|
||||||
// Remember the user's relay selection and settings
|
// do a hard refresh so everything gets totally cleared
|
||||||
connections.set($connections)
|
window.location = '/login'
|
||||||
settings.set($settings)
|
|
||||||
|
|
||||||
// Do a hard refresh so everything gets totally cleared
|
|
||||||
window.location = '/login'
|
|
||||||
}, 200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -83,7 +76,13 @@
|
||||||
const unsubUser = user.subscribe($user => {
|
const unsubUser = user.subscribe($user => {
|
||||||
if ($user && $user.pubkey !== prevPubkey) {
|
if ($user && $user.pubkey !== prevPubkey) {
|
||||||
relay.pool.syncNetwork()
|
relay.pool.syncNetwork()
|
||||||
relay.pool.listenForEvents('App/alerts', {'#p': [$user.pubkey], since: now()})
|
relay.pool.listenForEvents(
|
||||||
|
'App/alerts',
|
||||||
|
[{kinds: [1, 7], '#p': [$user.pubkey], since: mostRecentAlert}],
|
||||||
|
e => {
|
||||||
|
mostRecentAlert = Math.max(e.created_at, mostRecentAlert)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
prevPubkey = $user?.pubkey
|
prevPubkey = $user?.pubkey
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let note
|
export let note
|
||||||
export let depth = 0
|
export let depth = 0
|
||||||
export let anchorId = null
|
export let anchorId = null
|
||||||
|
export let showParent = true
|
||||||
export let invertColors = false
|
export let invertColors = false
|
||||||
|
|
||||||
let reply = null
|
let reply = null
|
||||||
|
@ -117,7 +118,7 @@
|
||||||
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
|
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6 flex flex-col gap-2">
|
<div class="ml-6 flex flex-col gap-2">
|
||||||
{#if findReply(note)}
|
{#if findReply(note) && showParent}
|
||||||
<small class="text-light">
|
<small class="text-light">
|
||||||
Reply to <Anchor on:click={goToParent}>{findReply(note).slice(0, 8)}</Anchor>
|
Reply to <Anchor on:click={goToParent}>{findReply(note).slice(0, 8)}</Anchor>
|
||||||
</small>
|
</small>
|
||||||
|
@ -184,7 +185,7 @@
|
||||||
{#if depth > 0}
|
{#if depth > 0}
|
||||||
{#each note.replies as r (r.id)}
|
{#each note.replies as r (r.id)}
|
||||||
<div class="ml-5 border-l border-solid border-medium">
|
<div class="ml-5 border-l border-solid border-medium">
|
||||||
<svelte:self note={r} depth={depth - 1} {invertColors} {anchorId} />
|
<svelte:self showParent={false} note={r} depth={depth - 1} {invertColors} {anchorId} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -39,7 +39,7 @@ db.events.process = async events => {
|
||||||
// Persist notes and reactions
|
// Persist notes and reactions
|
||||||
if (notesAndReactions.length > 0) {
|
if (notesAndReactions.length > 0) {
|
||||||
const persistentEvents = notesAndReactions
|
const persistentEvents = notesAndReactions
|
||||||
.map(e => ({...e, root: findRoot(e), reply: findReply(e), created_at: now()}))
|
.map(e => ({...e, root: findRoot(e), reply: findReply(e), loaded_at: now()}))
|
||||||
|
|
||||||
db.events.bulkPut(persistentEvents)
|
db.events.bulkPut(persistentEvents)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ db.events.process = async events => {
|
||||||
value: tag[1],
|
value: tag[1],
|
||||||
relay: tag[2],
|
relay: tag[2],
|
||||||
mark: tag[3],
|
mark: tag[3],
|
||||||
created_at: e.created_at,
|
loaded_at: now(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -107,5 +107,5 @@ db.events.process = async events => {
|
||||||
// On initial load, delete old event data
|
// On initial load, delete old event data
|
||||||
const threshold = now() - timedelta(30, 'days')
|
const threshold = now() - timedelta(30, 'days')
|
||||||
|
|
||||||
db.events.where('created_at').below(threshold).delete()
|
db.events.where('loaded_at').below(threshold).delete()
|
||||||
db.tags.where('created_at').below(threshold).delete()
|
db.tags.where('loaded_at').below(threshold).delete()
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import {propEq, uniqBy, prop, sortBy} from 'ramda'
|
import {propEq, uniqBy, prop, sortBy} from 'ramda'
|
||||||
import {onMount, onDestroy} from 'svelte'
|
import {onMount, onDestroy} from 'svelte'
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
|
import {alerts} from 'src/state/app'
|
||||||
import {findReply} from 'src/util/nostr'
|
import {findReply} from 'src/util/nostr'
|
||||||
import relay, {people, user} from 'src/relay'
|
import relay, {people, user} from 'src/relay'
|
||||||
import {alerts} from 'src/state/app'
|
|
||||||
import {now, timedelta, createScroller, Cursor, getLastSync} from 'src/util/misc'
|
import {now, timedelta, createScroller, Cursor, getLastSync} from 'src/util/misc'
|
||||||
import Spinner from "src/partials/Spinner.svelte"
|
import Spinner from "src/partials/Spinner.svelte"
|
||||||
import Note from 'src/partials/Note.svelte'
|
import Note from 'src/partials/Note.svelte'
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
sub = await relay.pool.listenForEvents(
|
sub = await relay.pool.listenForEvents(
|
||||||
'routes/Alerts',
|
'routes/Alerts',
|
||||||
[{kinds: [1, 7], '#p': [$user.pubkey], since: cursor.since}],
|
[{kinds: [1, 5, 7], '#p': [$user.pubkey], since: cursor.since}],
|
||||||
onEvent
|
onEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@
|
||||||
relay.getOrLoadNote(replyId)
|
relay.getOrLoadNote(replyId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alerts.set({since: now()})
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadNotes = async limit => {
|
const loadNotes = async limit => {
|
||||||
|
@ -94,7 +96,6 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear notification badge
|
|
||||||
alerts.set({since: now()})
|
alerts.set({since: now()})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,17 @@
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import {modal} from "src/state/app"
|
import {modal, settings} from "src/state/app"
|
||||||
import relay, {connections} from 'src/relay'
|
import relay, {connections} from 'src/relay'
|
||||||
|
|
||||||
let q = ""
|
let q = ""
|
||||||
let search
|
let search
|
||||||
|
|
||||||
const defaultRelays = [
|
fetch($settings.dufflepudUrl + '/relay').then(r => r.json()).then(({relays}) => {
|
||||||
"wss://nostr.zebedee.cloud",
|
for (const url of relays) {
|
||||||
"wss://nostr-pub.wellorder.net",
|
relay.db.relays.put({url})
|
||||||
"wss://relay.damus.io",
|
}
|
||||||
"wss://relay.grunch.dev",
|
})
|
||||||
"wss://nostr.sandwich.farm",
|
|
||||||
"wss://relay.nostr.ch",
|
|
||||||
"wss://nostr-relay.wlvs.space",
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const url of defaultRelays) {
|
|
||||||
relay.db.relays.put({url})
|
|
||||||
}
|
|
||||||
|
|
||||||
const knownRelays = relay.lq(() => relay.db.relays.toArray())
|
const knownRelays = relay.lq(() => relay.db.relays.toArray())
|
||||||
|
|
||||||
|
@ -72,7 +64,7 @@
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each (search(q) || []).slice(0, 10) as r}
|
{#each (search(q) || []).slice(0, 50) as r}
|
||||||
{#if !$connections.includes(r.url)}
|
{#if !$connections.includes(r.url)}
|
||||||
<div class="flex gap-2 justify-between">
|
<div class="flex gap-2 justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
@ -85,6 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
<small class="text-center">Found {($knownRelays || []).length} known relays</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -89,7 +89,7 @@ export const createScroller = loadMore => {
|
||||||
|
|
||||||
export const randomChoice = xs => xs[Math.floor(Math.random() * xs.length)]
|
export const randomChoice = xs => xs[Math.floor(Math.random() * xs.length)]
|
||||||
|
|
||||||
export const getLastSync = (k, fallback) => {
|
export const getLastSync = (k, fallback = 0) => {
|
||||||
const key = `${k}/lastSync`
|
const key = `${k}/lastSync`
|
||||||
const lastSync = getLocalJson(key) || fallback
|
const lastSync = getLocalJson(key) || fallback
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue