mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Remember message/chat notification read status
This commit is contained in:
parent
55d38ef113
commit
7916ed501c
@ -5,6 +5,7 @@
|
||||
- [x] Add purplepag.es to sign in flow
|
||||
- [x] Include people with only a display_name in search
|
||||
- [x] Fix AUTH over multiplextr
|
||||
- [x] Remember whether messages/notifications have been read
|
||||
|
||||
## 0.2.24
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Current
|
||||
|
||||
- [ ] Remember message/chat status
|
||||
- [ ] Add way to turn off likes/zaps
|
||||
- [ ] Remember joined rooms
|
||||
- [ ] Image classification
|
||||
- https://github.com/bhky/opennsfw2
|
||||
- [ ] Claim relays bounty
|
||||
@ -44,6 +44,7 @@
|
||||
|
||||
# UI/Features
|
||||
|
||||
- [ ] Use real links so cmd+click or right click work
|
||||
- [ ] Allow sharing of lists/following other people's lists
|
||||
- [ ] Add suggestion list for topics on compose
|
||||
- [ ] Badges link to https://badges.page/p/97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322
|
||||
|
@ -31,8 +31,7 @@ const setRelays = newRelays =>
|
||||
}),
|
||||
})
|
||||
|
||||
const setSettings = content =>
|
||||
new PublishableEvent(30078, {content, tags: [["d", "coracle/settings/v1"]]})
|
||||
const setAppData = (d, content) => new PublishableEvent(30078, {content, tags: [["d", d]]})
|
||||
|
||||
const setPetnames = petnames => new PublishableEvent(3, {tags: petnames})
|
||||
|
||||
@ -203,7 +202,7 @@ export default {
|
||||
authenticate,
|
||||
updateUser,
|
||||
setRelays,
|
||||
setSettings,
|
||||
setAppData,
|
||||
setPetnames,
|
||||
setMutes,
|
||||
createList,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type {MyEvent} from "src/util/types"
|
||||
import {sortBy, assoc, uniq, uniqBy, prop, propEq, groupBy, pluck} from "ramda"
|
||||
import {personKinds, findReplyId} from "src/util/nostr"
|
||||
import {without, sortBy, assoc, uniq, uniqBy, prop, propEq, groupBy, pluck} from "ramda"
|
||||
import {personKinds, appDataKeys, findReplyId} from "src/util/nostr"
|
||||
import {chunk} from "hurdak/lib/hurdak"
|
||||
import {batch, now, timedelta} from "src/util/misc"
|
||||
import {
|
||||
@ -107,10 +107,18 @@ const loadPeople = async (pubkeys, {relays = null, kinds = personKinds, force =
|
||||
|
||||
await Promise.all(
|
||||
chunk(256, pubkeys).map(async chunk => {
|
||||
await load({
|
||||
relays: sampleRelays(relays || getAllPubkeyWriteRelays(chunk), 0.5),
|
||||
filter: {kinds, authors: chunk},
|
||||
})
|
||||
const chunkRelays = sampleRelays(relays || getAllPubkeyWriteRelays(chunk), 0.5)
|
||||
const chunkFilter = [] as Array<Record<string, any>>
|
||||
|
||||
chunkFilter.push({kinds: without([30078], kinds), authors: chunk})
|
||||
|
||||
// Add a separate filter for app data so we're not pulling down other people's stuff,
|
||||
// or obsolete events of our own.
|
||||
if (kinds.includes(30078)) {
|
||||
chunkFilter.push({kinds: [30078], authors: chunk, "#d": appDataKeys})
|
||||
}
|
||||
|
||||
await load({relays: chunkRelays, filter: chunkFilter})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -149,27 +149,29 @@ addHandler(3, e => {
|
||||
|
||||
// User profile, except for events also handled for other users
|
||||
|
||||
const profileHandler = (key, getValue) => async e => {
|
||||
const profile = user.getProfile()
|
||||
|
||||
if (e.pubkey !== profile.pubkey) {
|
||||
return
|
||||
}
|
||||
|
||||
const updated_at_key = `${key}_updated_at`
|
||||
|
||||
if (e.created_at < profile?.[updated_at_key]) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = await getValue(e, profile)
|
||||
|
||||
// If we didn't get a value, don't update the key
|
||||
if (value) {
|
||||
user.profile.set({...profile, [key]: value, [updated_at_key]: e.created_at})
|
||||
const userHandler = cb => e => {
|
||||
if (e.pubkey === user.getPubkey()) {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
const profileHandler = (key, getValue) =>
|
||||
userHandler(async e => {
|
||||
const profile = user.getProfile()
|
||||
const updated_at_key = `${key}_updated_at`
|
||||
|
||||
if (e.created_at < profile?.[updated_at_key]) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = await getValue(e, profile)
|
||||
|
||||
// If we didn't get a value, don't update the key
|
||||
if (value) {
|
||||
user.profile.set({...profile, [key]: value, [updated_at_key]: e.created_at})
|
||||
}
|
||||
})
|
||||
|
||||
addHandler(
|
||||
2,
|
||||
profileHandler("relays", (e, p) => uniqByUrl(p.relays.concat({url: e.content})))
|
||||
@ -245,6 +247,15 @@ addHandler(
|
||||
})
|
||||
)
|
||||
|
||||
addHandler(
|
||||
30078,
|
||||
profileHandler("lastChecked", async (e, p) => {
|
||||
if (Tags.from(e).getMeta("d") === "coracle/last_checked/v1") {
|
||||
return {...p.lastChecked, ...(await keys.decryptJson(e.content))}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Rooms
|
||||
|
||||
addHandler(40, e => {
|
||||
@ -269,7 +280,7 @@ addHandler(40, e => {
|
||||
})
|
||||
|
||||
addHandler(41, e => {
|
||||
const roomId = Tags.from(e).type("e").values().first()
|
||||
const roomId = Tags.from(e).getMeta("e")
|
||||
|
||||
if (!roomId) {
|
||||
return
|
||||
|
@ -34,6 +34,7 @@ const profile = synced("agent/user/profile", {
|
||||
dufflepudUrl: import.meta.env.VITE_DUFFLEPUD_URL,
|
||||
multiplextrUrl: import.meta.env.VITE_MULTIPLEXTR_URL,
|
||||
},
|
||||
lastChecked: {},
|
||||
petnames: [],
|
||||
relays: [],
|
||||
mutes: [],
|
||||
@ -41,6 +42,7 @@ const profile = synced("agent/user/profile", {
|
||||
})
|
||||
|
||||
const settings = derived(profile, prop("settings"))
|
||||
const lastChecked = derived(profile, prop("lastChecked")) as Readable<Record<string, number>>
|
||||
const petnames = derived(profile, prop("petnames")) as Readable<Array<Array<string>>>
|
||||
const relays = derived(profile, prop("relays")) as Readable<Array<Relay>>
|
||||
const mutes = derived(profile, prop("mutes")) as Readable<Array<[string, string]>>
|
||||
@ -85,12 +87,29 @@ export default {
|
||||
async setSettings(settings) {
|
||||
profile.update($p => ({...$p, settings}))
|
||||
|
||||
if (keys.canSign()) {
|
||||
const content = await keys.encryptJson(settings)
|
||||
return this.setAppData("settings/v1", settings)
|
||||
},
|
||||
|
||||
return cmd.setSettings(content).publish(profileCopy.relays)
|
||||
// App data
|
||||
|
||||
lastChecked,
|
||||
async setAppData(key, content) {
|
||||
if (keys.canSign()) {
|
||||
const d = `coracle/${key}`
|
||||
const v = await keys.encryptJson(content)
|
||||
|
||||
return cmd.setAppData(d, v).publish(profileCopy.relays)
|
||||
}
|
||||
},
|
||||
setLastChecked(k, v) {
|
||||
profile.update($profile => {
|
||||
const lastChecked = {...$profile.lastChecked, [k]: v}
|
||||
|
||||
this.setAppData("last_checked/v1", lastChecked)
|
||||
|
||||
return {...$profile, lastChecked}
|
||||
})
|
||||
},
|
||||
|
||||
// Petnames
|
||||
|
||||
|
@ -57,7 +57,12 @@
|
||||
<ChatDetail entity={params.entity} />
|
||||
{/key}
|
||||
</Route>
|
||||
<Route path="/messages" component={MessagesList} />
|
||||
<Route path="/messages">
|
||||
<MessagesList activeTab="messages" />
|
||||
</Route>
|
||||
<Route path="/requests">
|
||||
<MessagesList activeTab="requests" />
|
||||
</Route>
|
||||
<Route path="/messages/:entity" let:params>
|
||||
{#key params.entity}
|
||||
<MessagesDetail entity={params.entity} />
|
||||
|
@ -4,11 +4,11 @@ import {nip19} from "nostr-tools"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {derived} from "svelte/store"
|
||||
import {writable} from "svelte/store"
|
||||
import {omit, pluck, sortBy, max, find, slice, propEq} from "ramda"
|
||||
import {max, omit, pluck, sortBy, find, slice, propEq} from "ramda"
|
||||
import {createMap, doPipe, first} from "hurdak/lib/hurdak"
|
||||
import {warn} from "src/util/logger"
|
||||
import {hash} from "src/util/misc"
|
||||
import {synced, now, timedelta} from "src/util/misc"
|
||||
import {now, timedelta} from "src/util/misc"
|
||||
import {Tags, isNotification, userKinds} from "src/util/nostr"
|
||||
import {findReplyId} from "src/util/nostr"
|
||||
import {modal, toast} from "src/partials/state"
|
||||
@ -96,21 +96,24 @@ export const feedsTab = writable("Follows")
|
||||
|
||||
// State
|
||||
|
||||
export const lastChecked = synced("app/alerts/lastChecked", {})
|
||||
|
||||
export const newNotifications = derived(
|
||||
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), lastChecked],
|
||||
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), user.lastChecked],
|
||||
([$lastNotification, $lastChecked]) => $lastNotification > ($lastChecked.notifications || 0)
|
||||
)
|
||||
|
||||
export const hasNewMessages = ({lastReceived, lastSent}, lastChecked) =>
|
||||
lastReceived > Math.max(lastSent, lastChecked || 0)
|
||||
|
||||
export const newDirectMessages = derived(
|
||||
[watch("contacts", t => t.all()), lastChecked],
|
||||
([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts))
|
||||
[watch("contacts", t => t.all()), user.lastChecked],
|
||||
([contacts, $lastChecked]) =>
|
||||
Boolean(find(c => hasNewMessages(c, $lastChecked[`dm/${c.pubkey}`]), contacts))
|
||||
)
|
||||
|
||||
export const newChatMessages = derived(
|
||||
[watch("rooms", t => t.all()), lastChecked],
|
||||
([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms))
|
||||
[watch("rooms", t => t.all()), user.lastChecked],
|
||||
([rooms, $lastChecked]) =>
|
||||
Boolean(find(r => hasNewMessages(r, $lastChecked[`chat/${r.id}`]), rooms))
|
||||
)
|
||||
|
||||
// Synchronization from events to state
|
||||
@ -126,23 +129,17 @@ const processMessages = async (pubkey, events) => {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
if (message.pubkey === pubkey) {
|
||||
const recipient = Tags.from(message).type("p").values().first()
|
||||
for (const message of messages) {
|
||||
const fromSelf = message.pubkey === pubkey
|
||||
const contactPubkey = fromSelf ? Tags.from(message).getMeta("p") : message.pubkey
|
||||
const contact = contacts.get(contactPubkey)
|
||||
const key = fromSelf ? "lastSent" : "lastReceived"
|
||||
|
||||
$lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at)
|
||||
contacts.patch({pubkey: recipient, accepted: true})
|
||||
} else {
|
||||
const contact = contacts.get(message.pubkey)
|
||||
const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at)
|
||||
|
||||
contacts.patch({pubkey: message.pubkey, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
contacts.patch({
|
||||
pubkey: contactPubkey,
|
||||
[key]: Math.max(contact?.[key] || 0, message.created_at),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const processChats = async (pubkey, events) => {
|
||||
@ -152,22 +149,14 @@ const processChats = async (pubkey, events) => {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
const id = Tags.from(message).getMeta("e")
|
||||
for (const message of messages) {
|
||||
const fromSelf = message.pubkey === pubkey
|
||||
const id = Tags.from(message).getMeta("e")
|
||||
const room = rooms.get(id)
|
||||
const key = fromSelf ? "lastSent" : "lastReceived"
|
||||
|
||||
if (message.pubkey === pubkey) {
|
||||
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)
|
||||
} else {
|
||||
const room = rooms.get(id)
|
||||
const lastMessage = Math.max(room?.lastMessage || 0, message.created_at)
|
||||
|
||||
rooms.patch({id, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
rooms.patch({id, [key]: Math.max(room?.[key] || 0, message.created_at)})
|
||||
}
|
||||
}
|
||||
|
||||
export const listen = async pubkey => {
|
||||
|
@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import {assoc} from "ramda"
|
||||
import {updateIn} from "hurdak/lib/hurdak"
|
||||
import {now, formatTimestamp} from "src/util/misc"
|
||||
import {toHex} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
@ -13,7 +11,6 @@
|
||||
import network from "src/agent/network"
|
||||
import {watch} from "src/agent/db"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {lastChecked} from "src/app/state"
|
||||
|
||||
export let entity
|
||||
|
||||
@ -21,6 +18,8 @@
|
||||
const room = watch("rooms", t => t.get(id) || {id})
|
||||
const getRelays = () => sampleRelays($room ? getRelaysForEventChildren($room) : [])
|
||||
|
||||
user.setLastChecked(`chat/${id}`, now())
|
||||
|
||||
const listenForMessages = onChunk =>
|
||||
network.listen({
|
||||
relays: getRelays(),
|
||||
@ -47,8 +46,6 @@
|
||||
}
|
||||
|
||||
document.title = $room.name
|
||||
|
||||
lastChecked.update(updateIn(assoc(id, now())))
|
||||
</script>
|
||||
|
||||
<Channel {loadMessages} {listenForMessages} {sendMessage}>
|
||||
|
@ -27,24 +27,12 @@
|
||||
<h2 class="text-lg">{room.name || ""}</h2>
|
||||
</div>
|
||||
{#if room.joined}
|
||||
<Anchor
|
||||
type="button"
|
||||
class="flex items-center gap-2"
|
||||
on:click={e => {
|
||||
e.stopPropagation()
|
||||
leave()
|
||||
}}>
|
||||
<Anchor type="button" preventDefault class="flex items-center gap-2" on:click={leave}>
|
||||
<i class="fa fa-right-from-bracket" />
|
||||
<span>Leave</span>
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor
|
||||
type="button"
|
||||
class="flex items-center gap-2"
|
||||
on:click={e => {
|
||||
e.stopPropagation()
|
||||
join()
|
||||
}}>
|
||||
<Anchor type="button" preventDefault class="flex items-center gap-2" on:click={join}>
|
||||
<i class="fa fa-right-to-bracket" />
|
||||
<span>Join</span>
|
||||
</Anchor>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {assoc} from "ramda"
|
||||
import {toHex, displayPerson} from "src/util/nostr"
|
||||
import {now, formatTimestamp} from "src/util/misc"
|
||||
import {Tags} from "src/util/nostr"
|
||||
@ -15,7 +14,6 @@
|
||||
import user from "src/agent/user"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {routes} from "src/app/state"
|
||||
import {lastChecked} from "src/app/state"
|
||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||
import PersonAbout from "src/app/shared/PersonAbout.svelte"
|
||||
|
||||
@ -25,17 +23,16 @@
|
||||
let pubkey = toHex(entity)
|
||||
let person = watch("people", () => getPersonWithFallback(pubkey))
|
||||
|
||||
lastChecked.update(assoc(pubkey, now()))
|
||||
user.setLastChecked(`dm/${pubkey}`, now())
|
||||
|
||||
const getRelays = () => {
|
||||
return sampleRelays(getAllPubkeyRelays([pubkey, user.getPubkey()]))
|
||||
}
|
||||
const getRelays = () => sampleRelays(getAllPubkeyRelays([pubkey, user.getPubkey()]))
|
||||
|
||||
const decryptMessages = async events => {
|
||||
const results = []
|
||||
|
||||
// Gotta do it in serial because of extension limitations
|
||||
for (const event of events) {
|
||||
const key = event.pubkey === pubkey ? pubkey : Tags.from(event).type("p").values().first()
|
||||
const key = event.pubkey === pubkey ? pubkey : Tags.from(event).getMeta("p")
|
||||
|
||||
results.push({...event, content: await crypt.decrypt(key, event.content)})
|
||||
}
|
||||
@ -76,7 +73,10 @@
|
||||
<Channel {loadMessages} {listenForMessages} {sendMessage}>
|
||||
<div slot="header" class="mb-2 flex h-20 items-start gap-4 overflow-hidden p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Anchor type="unstyled" class="fa fa-arrow-left cursor-pointer text-2xl" href="/messages" />
|
||||
<Anchor
|
||||
type="unstyled"
|
||||
class="fa fa-arrow-left cursor-pointer text-2xl"
|
||||
on:click={() => history.back()} />
|
||||
<PersonCircle person={$person} size={12} />
|
||||
</div>
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
@ -103,7 +103,8 @@
|
||||
})}>
|
||||
<div
|
||||
class={cx("inline-block max-w-xl rounded-2xl py-2 px-4", {
|
||||
"rounded-br-none bg-white text-end text-black": message.person.pubkey === user.getPubkey(),
|
||||
"rounded-br-none bg-gray-2 text-end text-gray-8":
|
||||
message.person.pubkey === user.getPubkey(),
|
||||
"rounded-bl-none bg-gray-7": message.person.pubkey !== user.getPubkey(),
|
||||
})}>
|
||||
<div class="break-words">
|
||||
|
@ -1,25 +1,19 @@
|
||||
<script>
|
||||
import {sortBy} from "ramda"
|
||||
import {toTitle} from "hurdak/lib/hurdak"
|
||||
import {navigate} from "svelte-routing"
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import MessagesListItem from "src/app/views/MessagesListItem.svelte"
|
||||
import {watch} from "src/agent/db"
|
||||
|
||||
let activeTab = "messages"
|
||||
let contacts = []
|
||||
export let activeTab = "messages"
|
||||
|
||||
const accepted = watch("contacts", t => t.all({lastSent: {$exists: true}}))
|
||||
const requests = watch("contacts", t => t.all({lastSent: {$exists: false}}))
|
||||
|
||||
const getContacts = tab =>
|
||||
sortBy(c => -c.lastMessage || 0, tab === "messages" ? $accepted : $requests)
|
||||
|
||||
$: contacts = getContacts(activeTab)
|
||||
|
||||
const setActiveTab = tab => {
|
||||
activeTab = tab
|
||||
}
|
||||
|
||||
const accepted = watch("contacts", t => t.all({accepted: true}))
|
||||
const requests = watch("contacts", t => t.all({accepted: {$ne: true}}))
|
||||
sortBy(c => -(c.lastSent || c.lastReceived || 0), tab === "messages" ? $accepted : $requests)
|
||||
|
||||
const getDisplay = tab => ({
|
||||
title: toTitle(tab),
|
||||
@ -30,7 +24,7 @@
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
<Tabs tabs={["messages", "requests"]} {activeTab} {setActiveTab} {getDisplay} />
|
||||
<Tabs tabs={["messages", "requests"]} {activeTab} setActiveTab={navigate} {getDisplay} />
|
||||
{#each getContacts(activeTab) as contact (contact.pubkey)}
|
||||
<MessagesListItem {contact} />
|
||||
{:else}
|
||||
|
@ -4,15 +4,17 @@
|
||||
import {ellipsize} from "hurdak/lib/hurdak"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {lastChecked} from "src/app/state"
|
||||
import user from "src/agent/user"
|
||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import {hasNewMessages} from "src/app/state"
|
||||
|
||||
export let contact
|
||||
|
||||
const newMessages = contact.lastMessage > $lastChecked[contact.pubkey]
|
||||
const {lastChecked} = user
|
||||
const person = getPersonWithFallback(contact.pubkey)
|
||||
const enter = () => navigate(`/messages/${nip19.npubEncode(contact.pubkey)}`)
|
||||
const newMessages = hasNewMessages(contact, $lastChecked[`dm/${contact.pubkey}`])
|
||||
</script>
|
||||
|
||||
<Card interactive on:click={enter}>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import {pluck, reverse, max, last, sortBy, assoc} from "ramda"
|
||||
import {throttle} from "throttle-debounce"
|
||||
import {pluck, reverse, max, last, sortBy} from "ramda"
|
||||
import {onMount} from "svelte"
|
||||
import {fly} from "svelte/transition"
|
||||
import {now, timedelta, createScroller} from "src/util/misc"
|
||||
@ -10,14 +11,16 @@
|
||||
import {watch} from "src/agent/db"
|
||||
import user from "src/agent/user"
|
||||
import {userEvents} from "src/agent/db"
|
||||
import {lastChecked} from "src/app/state"
|
||||
|
||||
let limit = 0
|
||||
let events = null
|
||||
|
||||
const {lastChecked} = user
|
||||
|
||||
const prevChecked = $lastChecked.notifications || 0
|
||||
const updateLastChecked = throttle(30_000, () => user.setLastChecked("notifications", now() + 30))
|
||||
const notifications = watch("notifications", t => {
|
||||
lastChecked.update(assoc("notifications", now()))
|
||||
updateLastChecked()
|
||||
|
||||
// Sort by rounded timestamp so we can group reactions to the same parent
|
||||
return reverse(
|
||||
|
@ -41,8 +41,8 @@
|
||||
|
||||
const stickToBottom = async cb => {
|
||||
const lastMessage = pluck("created_at", annotatedMessages).reduce(max, 0)
|
||||
const {scrollTop} = document.querySelector(".channel-messages")
|
||||
const shouldStick = scrollTop > -200
|
||||
const $channelMessages = document.querySelector(".channel-messages")
|
||||
const shouldStick = $channelMessages?.scrollTop > -200
|
||||
|
||||
await cb()
|
||||
|
||||
|
@ -7,6 +7,7 @@ import {invoiceAmount} from "src/util/lightning"
|
||||
|
||||
export const personKinds = [0, 2, 3, 10001, 10002]
|
||||
export const userKinds = personKinds.concat([10000, 30001, 30078])
|
||||
export const appDataKeys = ["coracle/settings/v1", "coracle/last_checked/v1"]
|
||||
|
||||
export class Tags {
|
||||
tags: Array<any>
|
||||
|
Loading…
Reference in New Issue
Block a user