mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-29 00:10:52 +00:00
Massage DM notifications
This commit is contained in:
parent
c46a4df261
commit
23160dff4b
@ -19,6 +19,7 @@ If you like Coracle and want to support its development, you can donate sats via
|
|||||||
- [x] NIP 05
|
- [x] NIP 05
|
||||||
- [x] Direct messages using NIP 04
|
- [x] Direct messages using NIP 04
|
||||||
- [ ] Add petnames for channels
|
- [ ] Add petnames for channels
|
||||||
|
- [ ] Add notifications for chat messages
|
||||||
- [ ] Deploy coracle relay, set better defaults
|
- [ ] Deploy coracle relay, set better defaults
|
||||||
- [ ] Image uploads
|
- [ ] Image uploads
|
||||||
- Use dufflepud. Default will charge via lightning and have a tos, others can self-host and skip that.
|
- 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)
|
- [ ] Attachments (a tag w/content type and url)
|
||||||
- [ ] Add Labs tab with cards for non-standard features
|
- [ ] Add Labs tab with cards for non-standard features
|
||||||
- Time travel - see events as of a date/time
|
- Time travel - see events as of a date/time
|
||||||
|
- [ ] Linkify bech32 entities
|
||||||
|
|
||||||
|
|
||||||
# Bugs
|
# Bugs
|
||||||
@ -53,10 +55,6 @@ If you like Coracle and want to support its development, you can donate sats via
|
|||||||
## Current
|
## Current
|
||||||
|
|
||||||
- [ ] Figure out migrations from previous version
|
- [ ] Figure out migrations from previous version
|
||||||
- [ ] Fix notes search
|
|
||||||
- [ ] Linkify bech32 entities
|
|
||||||
- [ ] Add notification for dms
|
|
||||||
- [ ] Default to network/following
|
|
||||||
|
|
||||||
## 0.2.7
|
## 0.2.7
|
||||||
|
|
||||||
|
@ -65,7 +65,9 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
hasNewMessages = Boolean(find(
|
hasNewMessages = Boolean(find(
|
||||||
([k, t]) => ($lastCheckedByPubkey[k] || 0) < t,
|
([k, t]) => {
|
||||||
|
return t > now() - timedelta(7, 'days') && ($lastCheckedByPubkey[k] || 0) < t
|
||||||
|
},
|
||||||
Object.entries($mostRecentByPubkey)
|
Object.entries($mostRecentByPubkey)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -211,11 +213,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{#if $user}
|
{#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">
|
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/chat">
|
||||||
<i class="fa-solid fa-message mr-2" /> Chat
|
<i class="fa-solid fa-message mr-2" /> Chat
|
||||||
{#if hasNewMessages}
|
{#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}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Dexie from 'dexie'
|
import Dexie, {liveQuery} from 'dexie'
|
||||||
import {pick} from 'ramda'
|
import {pick} from 'ramda'
|
||||||
import {nip05} from 'nostr-tools'
|
import {nip05} from 'nostr-tools'
|
||||||
import {writable} from 'svelte/store'
|
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 {now} from 'src/util/misc'
|
||||||
import {personKinds, Tags, roomAttrs} from 'src/util/nostr'
|
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')
|
export const db = new Dexie('agent/data/db')
|
||||||
|
|
||||||
db.version(13).stores({
|
db.version(13).stores({
|
||||||
|
@ -4,11 +4,11 @@ import {Tags} from 'src/util/nostr'
|
|||||||
import pool from 'src/agent/pool'
|
import pool from 'src/agent/pool'
|
||||||
import keys from 'src/agent/keys'
|
import keys from 'src/agent/keys'
|
||||||
import defaults from 'src/agent/defaults'
|
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})
|
Object.assign(window, {pool, db})
|
||||||
|
|
||||||
export {pool, keys, db, ready, people, getPerson}
|
export {pool, keys, lq, db, ready, people, getPerson}
|
||||||
|
|
||||||
export const user = derived(
|
export const user = derived(
|
||||||
[keys.pubkey, people],
|
[keys.pubkey, people],
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import {pluck} from 'ramda'
|
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 {listen as _listen} from 'src/agent'
|
||||||
import loaders from 'src/app/loaders'
|
import loaders from 'src/app/loaders'
|
||||||
|
|
||||||
let listener
|
let listener
|
||||||
|
|
||||||
|
const since = now() - timedelta(30, 'days')
|
||||||
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
|
const mostRecentByPubkey = synced('app/messages/mostRecentByPubkey', {})
|
||||||
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
|
const lastCheckedByPubkey = synced('app/messages/lastCheckedByPubkey', {})
|
||||||
|
|
||||||
@ -15,7 +16,8 @@ const listen = async (relays, pubkey) => {
|
|||||||
|
|
||||||
listener = await _listen(
|
listener = await _listen(
|
||||||
relays,
|
relays,
|
||||||
{kinds: [4], '#p': [pubkey]},
|
[{kinds: [4], authors: [pubkey], since},
|
||||||
|
{kinds: [4], '#p': [pubkey], since}],
|
||||||
batch(300, async events => {
|
batch(300, async events => {
|
||||||
if (events.length > 0) {
|
if (events.length > 0) {
|
||||||
await loaders.loadPeople(relays, pluck('pubkey', events))
|
await loaders.loadPeople(relays, pluck('pubkey', events))
|
||||||
|
@ -132,13 +132,18 @@
|
|||||||
<p class="text-sm text-light">{formatTimestamp(m.created_at)}</p>
|
<p class="text-sm text-light">{formatTimestamp(m.created_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class={cx("overflow-hidden text-ellipsis", {
|
<div class={cx("flex overflow-hidden text-ellipsis", {
|
||||||
'ml-6': type === 'chat',
|
'ml-12 justify-end': type === 'dm' && m.person.pubkey === $user.pubkey,
|
||||||
'rounded-2xl py-2 px-4': type === 'dm',
|
'mr-12': type === 'dm' && m.person.pubkey !== $user.pubkey,
|
||||||
'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,
|
|
||||||
})}>
|
})}>
|
||||||
{@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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@ -185,7 +190,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
<textarea
|
||||||
rows="4"
|
rows="4"
|
||||||
autofocus
|
autofocus
|
||||||
|
@ -1,14 +1,36 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {onMount} from 'svelte'
|
||||||
import {fly} from 'svelte/transition'
|
import {fly} from 'svelte/transition'
|
||||||
import {ellipsize} from 'hurdak/lib/hurdak'
|
import {ellipsize} from 'hurdak/lib/hurdak'
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import {displayPerson} from 'src/util/nostr'
|
import {displayPerson} from 'src/util/nostr'
|
||||||
|
import {now, timedelta} from 'src/util/misc'
|
||||||
|
import {messages} from 'src/app'
|
||||||
|
|
||||||
export let joined = false
|
export let joined = false
|
||||||
export let room
|
export let room
|
||||||
export let setRoom
|
export let setRoom
|
||||||
export let joinRoom
|
export let joinRoom
|
||||||
export let leaveRoom
|
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>
|
</script>
|
||||||
|
|
||||||
<li
|
<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"
|
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})" />
|
style="background-image: url({room.picture})" />
|
||||||
<div class="flex flex-grow flex-col justify-start gap-2">
|
<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">
|
<div class="flex gap-2 items-center">
|
||||||
{#if room.type === 'npub'}
|
{#if room.type === 'npub'}
|
||||||
<i class="fa fa-lock text-light" />
|
<i class="fa fa-lock text-light" />
|
||||||
@ -29,6 +51,16 @@
|
|||||||
<h2 class="text-lg">{room.name}</h2>
|
<h2 class="text-lg">{room.name}</h2>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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 room.type === 'note'}
|
||||||
{#if joined}
|
{#if joined}
|
||||||
<Anchor type="button" class="flex items-center gap-2" on:click={e => { e.stopPropagation(); leaveRoom(room.id) }}>
|
<Anchor type="button" class="flex items-center gap-2" on:click={e => { e.stopPropagation(); leaveRoom(room.id) }}>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import {uniq, pluck} from 'ramda'
|
import {without, uniq, sortBy, pluck} from 'ramda'
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from 'nostr-tools'
|
||||||
import {navigate} from "svelte-routing"
|
import {navigate} from "svelte-routing"
|
||||||
import {liveQuery} from 'dexie'
|
import {liveQuery} from 'dexie'
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
import {getRelays, getPerson, listen, db} from 'src/agent'
|
import {getRelays, user, lq, getPerson, listen, db} from 'src/agent'
|
||||||
import {modal} from 'src/app'
|
import {modal, messages} from 'src/app'
|
||||||
import loaders from 'src/app/loaders'
|
import loaders from 'src/app/loaders'
|
||||||
import Room from "src/partials/Room.svelte"
|
import Room from "src/partials/Room.svelte"
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
@ -17,16 +17,17 @@
|
|||||||
let q = ""
|
let q = ""
|
||||||
let roomsCount = 0
|
let roomsCount = 0
|
||||||
|
|
||||||
const rooms = liveQuery(async () => {
|
const {mostRecentByPubkey} = messages
|
||||||
const [rooms, messages] = await Promise.all([
|
|
||||||
db.rooms.where('joined').equals(1).toArray(),
|
const rooms = lq(async () => {
|
||||||
db.messages.toArray(),
|
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)
|
await loaders.loadPeople(getRelays(), pubkeys)
|
||||||
|
|
||||||
return pubkeys
|
return sortBy(k => -(mostRecentByPubkey[k] || 0), pubkeys)
|
||||||
.map(k => ({type: 'npub', id: k, ...getPerson(k, true)}))
|
.map(k => ({type: 'npub', id: k, ...getPerson(k, true)}))
|
||||||
.concat(rooms.map(room => ({type: 'note', ...room})))
|
.concat(rooms.map(room => ({type: 'note', ...room})))
|
||||||
})
|
})
|
||||||
@ -58,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const sub = listen(getRelays(), {kinds: [40, 41]})
|
const sub = listen(getRelays(), [{kinds: [40, 41]}])
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
sub.then(s => {
|
sub.then(s => {
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from 'nostr-tools'
|
||||||
import {sortBy, pluck} from 'ramda'
|
import {sortBy, pluck} from 'ramda'
|
||||||
import {personKinds} from 'src/util/nostr'
|
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 Channel from 'src/partials/Channel.svelte'
|
||||||
import {getRelays, user, db, listen, keys} from 'src/agent'
|
import {getRelays, user, db, listen, keys} from 'src/agent'
|
||||||
|
import {messages} from 'src/app'
|
||||||
import {routes} from 'src/app/ui'
|
import {routes} from 'src/app/ui'
|
||||||
import cmd from 'src/app/cmd'
|
import cmd from 'src/app/cmd'
|
||||||
|
|
||||||
@ -15,6 +16,8 @@
|
|||||||
let {data: pubkey} = nip19.decode(entity)
|
let {data: pubkey} = nip19.decode(entity)
|
||||||
let person = liveQuery(() => db.people.get(pubkey))
|
let person = liveQuery(() => db.people.get(pubkey))
|
||||||
|
|
||||||
|
messages.lastCheckedByPubkey.update($obj => ({...$obj, [pubkey]: now()}))
|
||||||
|
|
||||||
const decryptMessages = async events => {
|
const decryptMessages = async events => {
|
||||||
// Gotta do it in serial because of extension limitations
|
// Gotta do it in serial because of extension limitations
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
Loading…
Reference in New Issue
Block a user