mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add analytics
This commit is contained in:
parent
1a9a31b45b
commit
ef3b7f2d3d
14
README.md
14
README.md
@ -42,6 +42,11 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- [ ] Add Labs tab with cards for non-standard features
|
||||
- Time travel - see events as of a date/time
|
||||
|
||||
|
||||
# Bugs
|
||||
|
||||
- [ ] Sync mentions box and in-reply mentions
|
||||
|
||||
# Changelog
|
||||
|
||||
## Current
|
||||
@ -53,7 +58,14 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- [ ] Add petnames for channels
|
||||
- [ ] Add back button
|
||||
- [ ] Create Room -> open modal, choose dm or public room
|
||||
- [ ] Add DM button to profile pages
|
||||
- [x] Add DM button to profile pages
|
||||
- [ ] Linkify bech32 entities
|
||||
- [ ] linkify dm page header
|
||||
- [ ] Add lock/unlock icon to channel header
|
||||
- [ ] Add notification for dms
|
||||
- [ ] Default to network/following
|
||||
- [ ] Add analytics
|
||||
- [ ] Allow disabling error reporting/analytics
|
||||
|
||||
## 0.2.7
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
import "@fortawesome/fontawesome-free/css/fontawesome.css"
|
||||
import "@fortawesome/fontawesome-free/css/solid.css"
|
||||
|
||||
import {find, pluck} from 'ramda'
|
||||
import {find, nthArg, pluck} from 'ramda'
|
||||
import {onMount} from "svelte"
|
||||
import {writable, get} from "svelte/store"
|
||||
import {fly, fade} from "svelte/transition"
|
||||
@ -12,7 +12,7 @@
|
||||
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, alerts, messages} from "src/app"
|
||||
import {modal, toast, settings, logUsage, alerts, messages} from "src/app"
|
||||
import {routes} from "src/app/ui"
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
@ -105,9 +105,21 @@
|
||||
}
|
||||
})
|
||||
|
||||
const unsubHistory = globalHistory.listen(({location}) => {
|
||||
if (!location.hash) {
|
||||
// Remove identifying information, e.g. pubkeys, event ids, etc
|
||||
const name = location.pathname.slice(1)
|
||||
.replace(/(npub|nprofile|note|nevent)[^\/]+/g, (_, m) => `<${m}>`)
|
||||
|
||||
logUsage(btoa(['page', name].join(':')))
|
||||
}
|
||||
})
|
||||
|
||||
const unsubModal = modal.subscribe($modal => {
|
||||
// Keep scroll position on body, but don't allow scrolling
|
||||
if ($modal) {
|
||||
logUsage(btoa(['modal', $modal.type].join(':')))
|
||||
|
||||
// This is not idempotent, so don't duplicate it
|
||||
if (document.body.style.position !== 'fixed') {
|
||||
scrollY = window.scrollY
|
||||
@ -123,6 +135,7 @@
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
unsubHistory()
|
||||
unsubModal()
|
||||
}
|
||||
})
|
||||
@ -260,7 +273,7 @@
|
||||
<a
|
||||
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
|
||||
items-center border border-dark shadow-2xl cursor-pointer"
|
||||
on:click={() => modal.set({form: 'note/create'})}>
|
||||
on:click={() => modal.set({type: 'note/create'})}>
|
||||
<span class="fa-sold fa-plus fa-2xl" />
|
||||
</a>
|
||||
</div>
|
||||
@ -268,27 +281,24 @@
|
||||
|
||||
{#if $modal}
|
||||
<Modal onEscape={closeModal}>
|
||||
{#if $modal.note}
|
||||
{#if $modal.type === 'note/detail'}
|
||||
{#key $modal.note.id}
|
||||
<NoteDetail {...$modal} />
|
||||
{/key}
|
||||
{:else if $modal.form === 'note/create'}
|
||||
{:else if $modal.type === 'note/create'}
|
||||
<NoteCreate />
|
||||
{:else if $modal.form === 'relay'}
|
||||
{:else if $modal.type === 'relay/add'}
|
||||
<AddRelay />
|
||||
{:else if $modal.form === 'signUp'}
|
||||
{:else if $modal.type === 'signUp'}
|
||||
<SignUp />
|
||||
{:else if $modal.form === 'room/edit'}
|
||||
{:else if $modal.type === 'room/edit'}
|
||||
<ChatEdit {...$modal} />
|
||||
{:else if $modal.form === 'privkeyLogin'}
|
||||
{:else if $modal.type === 'login/privkey'}
|
||||
<PrivKeyLogin />
|
||||
{:else if $modal.form === 'pubkeyLogin'}
|
||||
{:else if $modal.type === 'login/pubkey'}
|
||||
<PubKeyLogin />
|
||||
{:else if $modal.form === 'person/settings'}
|
||||
{:else if $modal.type === 'person/settings'}
|
||||
<PersonSettings />
|
||||
{:else if $modal.message}
|
||||
<p class="text-white text-center py-12 pb-8">{$modal.message}</p>
|
||||
<Spinner />
|
||||
{/if}
|
||||
</Modal>
|
||||
{/if}
|
||||
|
@ -8,10 +8,10 @@ import {personKinds, Tags, roomAttrs} from 'src/util/nostr'
|
||||
|
||||
export const db = new Dexie('agent/data/db')
|
||||
|
||||
db.version(12).stores({
|
||||
db.version(13).stores({
|
||||
relays: '++url, name',
|
||||
alerts: '++id, created_at',
|
||||
messages: '++id, pubkey',
|
||||
messages: '++id, pubkey, recipient',
|
||||
people: '++pubkey',
|
||||
rooms: '++id, joined',
|
||||
})
|
||||
@ -157,7 +157,9 @@ const processRoomEvents = async events => {
|
||||
}
|
||||
|
||||
const processMessages = async events => {
|
||||
const messages = ensurePlural(events).filter(e => e.kind === 4)
|
||||
const messages = ensurePlural(events)
|
||||
.filter(e => e.kind === 4)
|
||||
.map(e => ({...e, recipient: Tags.from(e).type("p").values().first()}))
|
||||
|
||||
if (messages.length > 0) {
|
||||
await db.messages.bulkPut(messages)
|
||||
|
@ -16,10 +16,12 @@ const setPrivateKey = _privkey => {
|
||||
}
|
||||
|
||||
const setPublicKey = _pubkey => {
|
||||
signingFunction = async event => {
|
||||
const {sig} = await window.nostr.signEvent(event)
|
||||
if (window.nostr) {
|
||||
signingFunction = async event => {
|
||||
const {sig} = await window.nostr.signEvent(event)
|
||||
|
||||
return sig
|
||||
return sig
|
||||
}
|
||||
}
|
||||
|
||||
pubkey.set(_pubkey)
|
||||
@ -59,6 +61,7 @@ const getCrypt = () => {
|
||||
? nip04.decrypt($privkey, pubkey, message)
|
||||
: await window.nostr.nip04.decrypt(pubkey, message)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return `<Failed to decrypt message: ${e}>`
|
||||
}
|
||||
},
|
||||
|
@ -6,13 +6,13 @@ import {renderContent} from 'src/util/html'
|
||||
import {Tags, displayPerson, findReplyId} from 'src/util/nostr'
|
||||
import {user, people, getPerson, getRelays, keys} from 'src/agent'
|
||||
import defaults from 'src/agent/defaults'
|
||||
import {toast, routes, modal, settings} from 'src/app/ui'
|
||||
import {toast, routes, modal, settings, logUsage} from 'src/app/ui'
|
||||
import cmd from 'src/app/cmd'
|
||||
import alerts from 'src/app/alerts'
|
||||
import messages from 'src/app/messages'
|
||||
import loaders from 'src/app/loaders'
|
||||
|
||||
export {toast, modal, settings, alerts, messages}
|
||||
export {toast, modal, settings, alerts, messages, logUsage}
|
||||
|
||||
export const login = async ({privkey, pubkey}, usingExtension = false) => {
|
||||
if (privkey) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {prop} from "ramda"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {prop, nthArg} from "ramda"
|
||||
import {uuid} from "hurdak/lib/hurdak"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {nip19} from 'nostr-tools'
|
||||
@ -54,6 +55,32 @@ export const modal = {
|
||||
// Settings, alerts, etc
|
||||
|
||||
export const settings = synced("coracle/settings", {
|
||||
reportAnalytics: true,
|
||||
showLinkPreviews: true,
|
||||
dufflepudUrl: import.meta.env.VITE_DUFFLEPUD_URL,
|
||||
})
|
||||
|
||||
// Wait for bugsnag to be started in main
|
||||
setTimeout(() => {
|
||||
Bugsnag.addOnError(event => {
|
||||
if (window.location.host.startswith('localhost')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!get(settings).reportAnalytics) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const session = Math.random().toString().slice(2)
|
||||
|
||||
export const logUsage = name => {
|
||||
const {dufflepudUrl, reportAnalytics} = get(settings)
|
||||
|
||||
if (reportAnalytics) {
|
||||
fetch(`${dufflepudUrl}/usage/${session}/${name}`, {method: 'post' })
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import 'src/app.css'
|
||||
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import App from 'src/App.svelte'
|
||||
|
||||
// Annoying global always fails silently. Figure out an eslint rule instead
|
||||
window.find = null
|
||||
|
||||
Bugsnag.start({apiKey: "2ea412feabfe14dc9849c6f0b4fa7003"})
|
||||
|
||||
import App from 'src/App.svelte'
|
||||
|
||||
// Annoying global always fails silently. TODO: figure out an eslint rule instead
|
||||
window.find = null
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
})
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import cx from 'classnames'
|
||||
import {onMount} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {prop, path as getPath, reverse, uniqBy, sortBy, last} from 'ramda'
|
||||
import {formatTimestamp, createScroller, Cursor} from 'src/util/misc'
|
||||
import {formatTimestamp, sleep, createScroller, Cursor} from 'src/util/misc'
|
||||
import Badge from 'src/partials/Badge.svelte'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import {user, getPerson} from 'src/agent'
|
||||
@ -16,9 +17,12 @@
|
||||
export let listenForMessages
|
||||
export let sendMessage
|
||||
export let editRoom = null
|
||||
export let type
|
||||
console.log(editRoom)
|
||||
|
||||
let textarea
|
||||
let messages = []
|
||||
let loading = sleep(10_000)
|
||||
let annotatedMessages = []
|
||||
let showNewMessages = false
|
||||
let cursor = new Cursor()
|
||||
@ -62,6 +66,7 @@
|
||||
|
||||
const sub = listenForMessages(
|
||||
newMessages => stickToBottom('smooth', () => {
|
||||
loading = sleep(10_000)
|
||||
messages = messages.concat(newMessages)
|
||||
})
|
||||
)
|
||||
@ -74,6 +79,7 @@
|
||||
cursor.onChunk(events)
|
||||
|
||||
stickToBottom('auto', () => {
|
||||
loading = sleep(10_000)
|
||||
messages = events.concat(messages)
|
||||
})
|
||||
}
|
||||
@ -115,39 +121,47 @@
|
||||
|
||||
<div class="flex gap-4 h-full">
|
||||
<div class="relative w-full">
|
||||
<div class="flex flex-col py-32">
|
||||
<ul class="p-4 max-h-full flex-grow flex flex-col-reverse" name="messages">
|
||||
<div class="flex flex-col py-32 h-full">
|
||||
<ul class="p-4 h-full flex-grow flex flex-col-reverse justify-start" name="messages">
|
||||
{#each annotatedMessages as m (m.id)}
|
||||
<li in:fly={{y: 20}} class="py-1 flex flex-col gap-2">
|
||||
{#if m.showPerson}
|
||||
{#if type === 'chat' && m.showPerson}
|
||||
<div class="flex gap-4 items-center justify-between">
|
||||
<Badge person={m.person} />
|
||||
<p class="text-sm text-light">{formatTimestamp(m.created_at)}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ml-6 overflow-hidden text-ellipsis">
|
||||
<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,
|
||||
})}>
|
||||
{@html render(m, {showEntire: true})}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{#await loading}
|
||||
<Spinner>Looking for messages...</Spinner>
|
||||
<div class="h-48" />
|
||||
{/await}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="fixed z-10 top-0 pt-20 w-full p-4 border-b border-solid border-medium bg-dark flex gap-4">
|
||||
<div
|
||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({picture})" />
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="text-lg font-bold">{name || ''}</div>
|
||||
{#if editRoom}
|
||||
<small class="cursor-pointer" on:click={editRoom}>
|
||||
<i class="fa-solid fa-edit" /> Edit
|
||||
</small>
|
||||
{/if}
|
||||
<div class="fixed z-10 top-16 w-full lg:-ml-56 lg:pl-56 border-b border-solid border-medium bg-dark">
|
||||
<div class="p-4 flex gap-4">
|
||||
<div
|
||||
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({picture})" />
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="text-lg font-bold">{name || ''}</div>
|
||||
{#if editRoom}
|
||||
<small class="cursor-pointer" on:click={editRoom}>
|
||||
<i class="fa-solid fa-edit" /> Edit
|
||||
</small>
|
||||
{/if}
|
||||
</div>
|
||||
<div>{about || ''}</div>
|
||||
</div>
|
||||
<div>{about || ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed z-10 bottom-0 w-full flex bg-medium border-medium border-t border-solid border-dark">
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div
|
||||
class="py-2 px-3 flex flex-col gap-2 text-white cursor-pointer transition-all
|
||||
border border-solid border-black hover:border-medium hover:bg-dark"
|
||||
on:click={() => modal.set({note})}>
|
||||
on:click={() => modal.set({type: 'note', note})}>
|
||||
<div class="flex gap-2 items-center justify-between relative">
|
||||
<span class="cursor-pointer" on:click={openPopover}>
|
||||
{quantify(note.people.length, 'person', 'people')} liked your note.
|
||||
|
@ -2,7 +2,7 @@
|
||||
import cx from 'classnames'
|
||||
import extractUrls from 'extract-urls'
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {whereEq, uniq, pluck, reject, propEq, find} from 'ramda'
|
||||
import {whereEq, without, uniq, pluck, reject, propEq, find} from 'ramda'
|
||||
import {slide} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {quantify} from 'hurdak/lib/hurdak'
|
||||
@ -24,7 +24,7 @@
|
||||
export let invertColors = false
|
||||
|
||||
let reply = null
|
||||
let replyMentions = Tags.from(note).type("p").values().all()
|
||||
let replyMentions = without([$user.pubkey], Tags.from(note).type("p").values().all())
|
||||
|
||||
const links = $settings.showLinkPreviews ? extractUrls(note.content) || [] : null
|
||||
const showEntire = anchorId === note.id
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
const onClick = e => {
|
||||
if (!['I'].includes(e.target.tagName) && !e.target.closest('a')) {
|
||||
modal.set({note, relays})
|
||||
modal.set({type: 'note', note, relays})
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
const [id, url] = findReply(note).slice(1)
|
||||
const relays = getEventRelays(note).concat({url})
|
||||
|
||||
modal.set({note: {id}, relays})
|
||||
modal.set({type: 'note', note: {id}, relays})
|
||||
}
|
||||
|
||||
const react = async content => {
|
||||
@ -93,7 +93,7 @@
|
||||
}
|
||||
|
||||
const removeMention = pubkey => {
|
||||
replyMentions = reject(p => p === pubkey, replyMentions)
|
||||
replyMentions = without([pubkey], replyMentions)
|
||||
}
|
||||
|
||||
const resetReply = () => {
|
||||
@ -195,6 +195,9 @@
|
||||
</div>
|
||||
{#if replyMentions.length > 0}
|
||||
<div class="text-white text-sm p-2 rounded-b border-t-0 border border-solid border-medium">
|
||||
<div class="inline-block border-r border-solid border-medium pl-1 pr-3 mr-2">
|
||||
<i class="fa fa-at" />
|
||||
</div>
|
||||
{#each replyMentions as p}
|
||||
<div class="inline-block py-1 px-2 mr-1 mb-2 rounded-full border border-solid border-light">
|
||||
<i class="fa fa-times cursor-pointer" on:click|stopPropagation={() => removeMention(p)} />
|
||||
|
@ -21,7 +21,7 @@
|
||||
<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 gap-2 items-center">
|
||||
{#if room.type === 'private'}
|
||||
{#if room.type === 'npub'}
|
||||
<i class="fa fa-lock text-light" />
|
||||
<h2 class="text-lg">{displayPerson(room)}</h2>
|
||||
{:else}
|
||||
@ -29,7 +29,7 @@
|
||||
<h2 class="text-lg">{room.name}</h2>
|
||||
{/if}
|
||||
</div>
|
||||
{#if room.type === 'public'}
|
||||
{#if room.type === 'note'}
|
||||
{#if joined}
|
||||
<Anchor type="button" class="flex items-center gap-2" on:click={e => { e.stopPropagation(); leaveRoom(room.id) }}>
|
||||
<i class="fa fa-right-from-bracket" />
|
||||
|
@ -75,7 +75,7 @@
|
||||
<i class="fa fa-server fa-lg" />
|
||||
<h2 class="staatliches text-2xl">Your rooms</h2>
|
||||
</div>
|
||||
<Anchor type="button-accent" on:click={() => modal.set({form: 'room/edit'})}>
|
||||
<Anchor type="button-accent" on:click={() => modal.set({type: 'room/edit'})}>
|
||||
<i class="fa-solid fa-plus" /> Create Room
|
||||
</Anchor>
|
||||
</div>
|
||||
@ -103,4 +103,6 @@
|
||||
Showing {Math.min(50, roomsCount)} of {roomsCount} known rooms
|
||||
</small>
|
||||
</Content>
|
||||
{:else}
|
||||
<Spinner />
|
||||
{/if}
|
||||
|
@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
const editRoom = () => {
|
||||
modal.set({form: 'room/edit', room: $room})
|
||||
modal.set({type: 'room/edit', room: $room})
|
||||
}
|
||||
|
||||
const sendMessage = content =>
|
||||
@ -47,6 +47,7 @@
|
||||
</script>
|
||||
|
||||
<Channel
|
||||
type="chat"
|
||||
name={$room?.name}
|
||||
about={$room?.about}
|
||||
picture={$room?.picture}
|
||||
|
@ -11,16 +11,16 @@
|
||||
if (window.nostr) {
|
||||
await login({pubkey: await window.nostr.getPublicKey()}, true)
|
||||
} else {
|
||||
modal.set({form: 'privkeyLogin'})
|
||||
modal.set({type: 'login/privkey'})
|
||||
}
|
||||
}
|
||||
|
||||
const signUp = () => {
|
||||
modal.set({form: 'signUp'})
|
||||
modal.set({type: 'signUp'})
|
||||
}
|
||||
|
||||
const pubkeyLogIn = () => {
|
||||
modal.set({form: 'pubkeyLogin'})
|
||||
modal.set({type: 'login/pubkey'})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script>
|
||||
import {liveQuery} from 'dexie'
|
||||
import {nip19} from 'nostr-tools'
|
||||
import {assoc, propEq, mergeRight} from 'ramda'
|
||||
import {now, batch} from 'src/util/misc'
|
||||
import {sortBy, pluck} from 'ramda'
|
||||
import {personKinds} from 'src/util/nostr'
|
||||
import {batch} from 'src/util/misc'
|
||||
import Channel from 'src/partials/Channel.svelte'
|
||||
import {getRelays, user, db, listen, load, keys} from 'src/agent'
|
||||
import {getRelays, user, db, listen, keys} from 'src/agent'
|
||||
import cmd from 'src/app/cmd'
|
||||
|
||||
export let entity
|
||||
@ -13,41 +13,52 @@
|
||||
let crypt = keys.getCrypt()
|
||||
let {data: pubkey} = nip19.decode(entity)
|
||||
let person = liveQuery(() => db.people.get(pubkey))
|
||||
let filters = [
|
||||
{kinds: [4], authors: [$user.pubkey], '#p': [pubkey]},
|
||||
{kinds: [4], authors: [pubkey], '#p': [$user.pubkey]}]
|
||||
|
||||
const decryptMessages = async messages => {
|
||||
const decryptMessages = async events => {
|
||||
// Gotta do it in serial because of extension limitations
|
||||
for (const message of messages) {
|
||||
message.content = await crypt.decrypt(message.pubkey, message.content)
|
||||
console.log(message.content)
|
||||
for (const event of events) {
|
||||
const key = event.pubkey === pubkey ? pubkey : event.recipient
|
||||
|
||||
event.content = await crypt.decrypt(key, event.content)
|
||||
}
|
||||
|
||||
return messages
|
||||
return events
|
||||
}
|
||||
|
||||
const listenForMessages = cb => listen(
|
||||
getRelays(),
|
||||
[{kinds: personKinds, authors: [pubkey]},
|
||||
...filters.map(assoc('since', now()))],
|
||||
batch(300, events => {
|
||||
return cb(decryptMessages(events.filter(propEq('kind', 4))))
|
||||
{kinds: [4], authors: [$user.pubkey], '#p': [pubkey]},
|
||||
{kinds: [4], authors: [pubkey], '#p': [$user.pubkey]}],
|
||||
batch(300, async events => {
|
||||
// Reload from db since we annotate messages there
|
||||
const messageIds = pluck('id', events.filter(e => e.kind === 4))
|
||||
const messages = await db.messages.where('id').anyOf(messageIds).toArray()
|
||||
|
||||
cb(await decryptMessages(messages))
|
||||
})
|
||||
)
|
||||
|
||||
const loadMessages = async ({until, limit}) => {
|
||||
const messages = await load(getRelays(), filters.map(mergeRight({until, limit})))
|
||||
const fromThem = await db.messages.where('pubkey').equals(pubkey).toArray()
|
||||
const toThem = await db.messages.where('recipient').equals(pubkey).toArray()
|
||||
const events = fromThem.concat(toThem).filter(e => e.created_at < until)
|
||||
const messages = sortBy(e => -e.created_at, events).slice(0, limit)
|
||||
|
||||
return await decryptMessages(messages)
|
||||
}
|
||||
|
||||
const sendMessage = async content => {
|
||||
const cyphertext = await crypt.encrypt(pubkey, content)
|
||||
const event = await cmd.createDirectMessage(getRelays(), pubkey, cyphertext)
|
||||
|
||||
const sendMessage = content =>
|
||||
cmd.createDirectMessage(getRelays(), pubkey, content)
|
||||
// Return unencrypted content so we can display it immediately
|
||||
return {...event, content}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Channel
|
||||
type="dm"
|
||||
name={$person?.name}
|
||||
about={$person?.about}
|
||||
picture={$person?.picture}
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {displayPerson} from 'src/util/nostr'
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Notes from "src/views/person/Notes.svelte"
|
||||
@ -76,7 +77,7 @@
|
||||
}
|
||||
|
||||
const openAdvanced = () => {
|
||||
modal.set({form: 'person/settings', person})
|
||||
modal.set({type: 'person/settings', person})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -93,8 +94,8 @@
|
||||
<div
|
||||
class="overflow-hidden w-32 h-32 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({person.picture})" />
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="flex flex-col gap-4 flex-grow">
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<div class="flex-grow flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-2xl">{displayPerson(person)}</h1>
|
||||
@ -108,21 +109,21 @@
|
||||
</div>
|
||||
<div class="whitespace-nowrap flex gap-4 items-center">
|
||||
{#if $user?.pubkey === pubkey && keys.canSign()}
|
||||
<a href="/profile" class="cursor-pointer text-sm">
|
||||
<i class="fa-solid fa-edit" /> Edit
|
||||
</a>
|
||||
{/if}
|
||||
{#if $user && $user.pubkey !== pubkey}
|
||||
<i class="fa-solid fa-sliders cursor-pointer" on:click={openAdvanced} />
|
||||
{/if}
|
||||
{#if $user?.petnames && keys.canSign()}
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<Anchor type="button" href="/profile">
|
||||
<i class="fa-solid fa-edit" /> Edit profile
|
||||
</Anchor>
|
||||
{:else if $user && keys.canSign()}
|
||||
<Anchor type="button" on:click={openAdvanced}>
|
||||
<i class="fa-solid fa-sliders" />
|
||||
</Anchor>
|
||||
<Anchor type="button" href={`/messages/${npub}`}>
|
||||
<i class="fa-solid fa-envelope" />
|
||||
</Anchor>
|
||||
{#if following}
|
||||
<Button on:click={unfollow}>Unfollow</Button>
|
||||
{:else}
|
||||
<Button on:click={follow}>Follow</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,7 +73,7 @@
|
||||
<i class="fa fa-server fa-lg" />
|
||||
<h2 class="staatliches text-2xl">Your relays</h2>
|
||||
</div>
|
||||
<Anchor type="button" on:click={() => modal.set({form: 'relay', url: q})}>
|
||||
<Anchor type="button" on:click={() => modal.set({type: 'relay/add', url: q})}>
|
||||
<i class="fa-solid fa-plus" /> Add Relay
|
||||
</Anchor>
|
||||
</div>
|
||||
|
@ -56,6 +56,16 @@
|
||||
hosting images and loading link previews.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-2 items-center">
|
||||
<strong>Report Errors and Analytics</strong>
|
||||
<Toggle bind:value={values.reportAnalytics} />
|
||||
</div>
|
||||
<p class="text-sm text-light">
|
||||
Keep this enabled if you would like the Coracle developers to be able to
|
||||
know what features are used, and to diagnose and fix bugs.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="submit" class="text-center">Save</Button>
|
||||
</div>
|
||||
</Content>
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script>
|
||||
import {onMount} from "svelte"
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {stripExifData} from "src/util/html"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Textarea from "src/partials/Textarea.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import {getRelays, db} from 'src/agent'
|
||||
import {toast} from "src/app"
|
||||
import {toast, modal} from "src/app"
|
||||
import cmd from "src/app/cmd"
|
||||
|
||||
export let room = {}
|
||||
@ -34,7 +33,7 @@
|
||||
if (!room.name) {
|
||||
toast.show("error", "Please enter a name for your room.")
|
||||
} else {
|
||||
const event = room.id
|
||||
room.id
|
||||
? await cmd.updateRoom(getRelays(), room)
|
||||
: await cmd.createRoom(getRelays(), room)
|
||||
|
||||
@ -42,7 +41,7 @@
|
||||
|
||||
toast.show("info", `Your room has been ${room.id ? 'updated' : 'created'}!`)
|
||||
|
||||
navigate(`/chat/${room.id || event.id}`)
|
||||
modal.set(null)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -9,8 +9,9 @@
|
||||
import {modal} from 'src/app'
|
||||
import cmd from 'src/app/cmd'
|
||||
|
||||
const muffle = $user.muffle || []
|
||||
const muffleOptions = ['Never', 'Sometimes', 'Often', 'Always']
|
||||
const muffleValue = parseFloat(first($user.muffle.filter(t => t[1] === $modal.person.pubkey).map(last)) || 1)
|
||||
const muffleValue = parseFloat(first(muffle.filter(t => t[1] === $modal.person.pubkey).map(last)) || 1)
|
||||
|
||||
const values = {
|
||||
// Scale up to integers for each choice we have
|
||||
@ -22,7 +23,7 @@
|
||||
|
||||
// Scale back down to a decimal based on string value
|
||||
const muffleValue = muffleOptions.indexOf(values.muffle) / 3
|
||||
const muffleTags = $user.muffle
|
||||
const muffleTags = muffle
|
||||
.filter(t => t[1] !== $modal.person.pubkey)
|
||||
.concat([["p", $modal.person.pubkey, muffleValue.toString()]])
|
||||
.filter(t => last(t) !== "1")
|
||||
|
Loading…
Reference in New Issue
Block a user