Massage DM notifications

This commit is contained in:
Jonathan Staab 2023-01-21 13:14:25 -08:00
parent c46a4df261
commit 23160dff4b
9 changed files with 83 additions and 32 deletions

View File

@ -19,6 +19,7 @@ If you like Coracle and want to support its development, you can donate sats via
- [x] NIP 05
- [x] Direct messages using NIP 04
- [ ] Add petnames for channels
- [ ] Add notifications for chat messages
- [ ] Deploy coracle relay, set better defaults
- [ ] Image uploads
- Use dufflepud. Default will charge via lightning and have a tos, others can self-host and skip that.
@ -42,6 +43,7 @@ If you like Coracle and want to support its development, you can donate sats via
- [ ] Attachments (a tag w/content type and url)
- [ ] Add Labs tab with cards for non-standard features
- Time travel - see events as of a date/time
- [ ] Linkify bech32 entities
# Bugs
@ -53,10 +55,6 @@ If you like Coracle and want to support its development, you can donate sats via
## Current
- [ ] Figure out migrations from previous version
- [ ] Fix notes search
- [ ] Linkify bech32 entities
- [ ] Add notification for dms
- [ ] Default to network/following
## 0.2.7

View File

@ -65,7 +65,9 @@
$: {
hasNewMessages = Boolean(find(
([k, t]) => ($lastCheckedByPubkey[k] || 0) < t,
([k, t]) => {
return t > now() - timedelta(7, 'days') && ($lastCheckedByPubkey[k] || 0) < t
},
Object.entries($mostRecentByPubkey)
))
}
@ -211,11 +213,11 @@
</a>
</li>
{#if $user}
<li class="cursor-pointer">
<li class="cursor-pointer relative">
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/chat">
<i class="fa-solid fa-message mr-2" /> Chat
{#if hasNewMessages}
<div class="w-2 h-2 rounded bg-accent absolute top-3 left-6" />
<div class="w-2 h-2 rounded bg-accent absolute top-2 left-7" />
{/if}
</a>
</li>

View File

@ -1,4 +1,4 @@
import Dexie from 'dexie'
import Dexie, {liveQuery} from 'dexie'
import {pick} from 'ramda'
import {nip05} from 'nostr-tools'
import {writable} from 'svelte/store'
@ -6,6 +6,14 @@ import {noop, ensurePlural, createMap, switcherFn} from 'hurdak/lib/hurdak'
import {now} from 'src/util/misc'
import {personKinds, Tags, roomAttrs} from 'src/util/nostr'
export const lq = cb => liveQuery(async () => {
try {
return await cb()
} catch (e) {
console.error(e)
}
})
export const db = new Dexie('agent/data/db')
db.version(13).stores({

View File

@ -4,11 +4,11 @@ import {Tags} from 'src/util/nostr'
import pool from 'src/agent/pool'
import keys from 'src/agent/keys'
import defaults from 'src/agent/defaults'
import {db, people, ready, getPerson, processEvents} from 'src/agent/data'
import {lq, db, people, ready, getPerson, processEvents} from 'src/agent/data'
Object.assign(window, {pool, db})
export {pool, keys, db, ready, people, getPerson}
export {pool, keys, lq, db, ready, people, getPerson}
export const user = derived(
[keys.pubkey, people],

View File

@ -1,10 +1,11 @@
import {pluck} from 'ramda'
import {synced, batch} from 'src/util/misc'
import {synced, now, timedelta, batch} from 'src/util/misc'
import {listen as _listen} from 'src/agent'
import loaders from 'src/app/loaders'
let listener
const since = now() - timedelta(30, 'days')
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
@ -15,7 +16,8 @@ const listen = async (relays, pubkey) => {
listener = await _listen(
relays,
{kinds: [4], '#p': [pubkey]},
[{kinds: [4], authors: [pubkey], since},
{kinds: [4], '#p': [pubkey], since}],
batch(300, async events => {
if (events.length > 0) {
await loaders.loadPeople(relays, pluck('pubkey', events))

View File

@ -132,13 +132,18 @@
<p class="text-sm text-light">{formatTimestamp(m.created_at)}</p>
</div>
{/if}
<div class={cx("overflow-hidden text-ellipsis", {
'ml-6': type === 'chat',
'rounded-2xl py-2 px-4': type === 'dm',
'ml-12 bg-light text-black rounded-br-none': type === 'dm' && m.person.pubkey === $user.pubkey,
'mr-12 bg-dark rounded-bl-none': type === 'dm' && m.person.pubkey !== $user.pubkey,
<div class={cx("flex overflow-hidden text-ellipsis", {
'ml-12 justify-end': type === 'dm' && m.person.pubkey === $user.pubkey,
'mr-12': type === 'dm' && m.person.pubkey !== $user.pubkey,
})}>
{@html render(m, {showEntire: true})}
<div class={cx({
'ml-6': type === 'chat',
'rounded-2xl py-2 px-4 flex max-w-xl': type === 'dm',
'bg-light text-black rounded-br-none': type === 'dm' && m.person.pubkey === $user.pubkey,
'bg-dark rounded-bl-none': type === 'dm' && m.person.pubkey !== $user.pubkey,
})}>
{@html render(m, {showEntire: true})}
</div>
</div>
</li>
{/each}
@ -185,7 +190,7 @@
</div>
</div>
</div>
<div class="fixed z-10 bottom-0 w-full flex bg-medium border-medium border-t border-solid border-dark">
<div class="fixed z-10 bottom-0 w-full flex bg-medium border-medium border-t border-solid border-dark lg:-ml-56 lg:pl-56">
<textarea
rows="4"
autofocus

View File

@ -1,14 +1,36 @@
<script>
import {onMount} from 'svelte'
import {fly} from 'svelte/transition'
import {ellipsize} from 'hurdak/lib/hurdak'
import Anchor from 'src/partials/Anchor.svelte'
import {displayPerson} from 'src/util/nostr'
import {now, timedelta} from 'src/util/misc'
import {messages} from 'src/app'
export let joined = false
export let room
export let setRoom
export let joinRoom
export let leaveRoom
let hasNewMessages = false
const {mostRecentByPubkey, lastCheckedByPubkey} = messages
onMount(() => {
const interval = setInterval(() => {
// TODO notifications for channel messages
if (room.type === 'npub') {
const mostRecent = $mostRecentByPubkey[room.pubkey] || Infinity
const lastChecked = $lastCheckedByPubkey[room.pubkey] || 0
// Include a cut-off since we lose read receipts every log out
hasNewMessages = mostRecent > now() - timedelta(7, 'days') && lastChecked < mostRecent
}
}, 1000)
return () => clearInterval(interval)
})
</script>
<li
@ -19,7 +41,7 @@
class="overflow-hidden w-14 h-14 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({room.picture})" />
<div class="flex flex-grow flex-col justify-start gap-2">
<div class="flex flex-grow items-center justify-between gap-2">
<div class="flex flex-grow items-start justify-between gap-2">
<div class="flex gap-2 items-center">
{#if room.type === 'npub'}
<i class="fa fa-lock text-light" />
@ -29,6 +51,16 @@
<h2 class="text-lg">{room.name}</h2>
{/if}
</div>
{#if joined}
{#if hasNewMessages}
<div class="relative">
<i class="fa fa-bell" />
<div class="absolute top-0 right-0 mt-1 w-1 h-1 bg-accent rounded-full" />
</div>
{:else}
<i class="fa fa-bell text-light" />
{/if}
{/if}
{#if room.type === 'note'}
{#if joined}
<Anchor type="button" class="flex items-center gap-2" on:click={e => { e.stopPropagation(); leaveRoom(room.id) }}>

View File

@ -1,12 +1,12 @@
<script>
import {uniq, pluck} from 'ramda'
import {without, uniq, sortBy, pluck} from 'ramda'
import {onMount} from "svelte"
import {nip19} from 'nostr-tools'
import {navigate} from "svelte-routing"
import {liveQuery} from 'dexie'
import {fuzzy} from "src/util/misc"
import {getRelays, getPerson, listen, db} from 'src/agent'
import {modal} from 'src/app'
import {getRelays, user, lq, getPerson, listen, db} from 'src/agent'
import {modal, messages} from 'src/app'
import loaders from 'src/app/loaders'
import Room from "src/partials/Room.svelte"
import Input from "src/partials/Input.svelte"
@ -17,16 +17,17 @@
let q = ""
let roomsCount = 0
const rooms = liveQuery(async () => {
const [rooms, messages] = await Promise.all([
db.rooms.where('joined').equals(1).toArray(),
db.messages.toArray(),
])
const {mostRecentByPubkey} = messages
const rooms = lq(async () => {
const rooms = await db.rooms.where('joined').equals(1).toArray()
const messages = await db.messages.toArray()
const pubkeys = without([$user.pubkey], uniq(pluck('pubkey', messages)))
const pubkeys = uniq(pluck('pubkey', messages))
await loaders.loadPeople(getRelays(), pubkeys)
return pubkeys
return sortBy(k => -(mostRecentByPubkey[k] || 0), pubkeys)
.map(k => ({type: 'npub', id: k, ...getPerson(k, true)}))
.concat(rooms.map(room => ({type: 'note', ...room})))
})
@ -58,7 +59,7 @@
}
onMount(() => {
const sub = listen(getRelays(), {kinds: [40, 41]})
const sub = listen(getRelays(), [{kinds: [40, 41]}])
return () => {
sub.then(s => {

View File

@ -3,9 +3,10 @@
import {nip19} from 'nostr-tools'
import {sortBy, pluck} from 'ramda'
import {personKinds} from 'src/util/nostr'
import {batch} from 'src/util/misc'
import {batch, now} from 'src/util/misc'
import Channel from 'src/partials/Channel.svelte'
import {getRelays, user, db, listen, keys} from 'src/agent'
import {messages} from 'src/app'
import {routes} from 'src/app/ui'
import cmd from 'src/app/cmd'
@ -15,6 +16,8 @@
let {data: pubkey} = nip19.decode(entity)
let person = liveQuery(() => db.people.get(pubkey))
messages.lastCheckedByPubkey.update($obj => ({...$obj, [pubkey]: now()}))
const decryptMessages = async events => {
// Gotta do it in serial because of extension limitations
for (const event of events) {