mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Get main notes stuff working
This commit is contained in:
parent
0bfe584bab
commit
9f6601cbdc
@ -37,4 +37,8 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
|
||||
|
||||
# Workers
|
||||
|
||||
- [ ] Check firefox
|
||||
- [ ] Check firefox - in dev it won't work, but it should in production
|
||||
- [ ] Re-implement muffle
|
||||
- https://vitejs.dev/guide/features.html#web-workers
|
||||
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
||||
- https://web.dev/module-workers/
|
||||
|
@ -54,6 +54,8 @@
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('unhandledrejection', e => console.error(e))
|
||||
|
||||
onMount(() => {
|
||||
// Poll for new notifications
|
||||
(async function pollForNotifications() {
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import cx from 'classnames'
|
||||
import {find, uniqBy, prop, whereEq} from 'ramda'
|
||||
import {onMount} from 'svelte'
|
||||
import {liveQuery} from 'dexie'
|
||||
import {slide} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {hasParent, findLink} from 'src/util/html'
|
||||
import {renderNote} from 'src/util/notes'
|
||||
import Preview from 'src/partials/Preview.svelte'
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import relay from 'src/relay'
|
||||
import {dispatch} from "src/state/dispatch"
|
||||
import {findReply} from "src/state/nostr"
|
||||
import {accounts, settings, modal} from "src/state/app"
|
||||
@ -23,21 +23,17 @@
|
||||
export let showEntire = false
|
||||
export let invertColors = false
|
||||
|
||||
let link = null
|
||||
let like = null
|
||||
let flag = null
|
||||
let reply = null
|
||||
let interactive = null
|
||||
|
||||
$: {
|
||||
like = find(e => e.pubkey === $user?.pubkey && e.content === "+", note.reactions)
|
||||
flag = find(e => e.pubkey === $user?.pubkey && e.content === "-", note.reactions)
|
||||
interactive = !anchorId || anchorId !== note.id
|
||||
}
|
||||
const link = $settings.showLinkPreviews ? findLink(note.content) : null
|
||||
const interactive = !anchorId || anchorId !== note.id
|
||||
const like = liveQuery(() => relay.findReaction(note.id, {content: "+", pubkey: $user?.pubkey}))
|
||||
const flag = liveQuery(() => relay.findReaction(note.id, {content: "-", pubkey: $user?.pubkey}))
|
||||
const likes = liveQuery(() => relay.countReactions(note.id, {content: "+"}))
|
||||
const flags = liveQuery(() => relay.countReactions(note.id, {content: "-"}))
|
||||
const replies = liveQuery(() => relay.filterReplies(note.id))
|
||||
|
||||
onMount(async () => {
|
||||
link = $settings.showLinkPreviews ? findLink(note.content) : null
|
||||
})
|
||||
relay.ensureContext(note)
|
||||
|
||||
const onClick = e => {
|
||||
if (!['I'].includes(e.target.tagName) && !hasParent('a', e.target)) {
|
||||
@ -111,10 +107,10 @@
|
||||
Reply to <Anchor on:click={goToParent}>{findReply(note).slice(0, 8)}</Anchor>
|
||||
</small>
|
||||
{/if}
|
||||
{#if flag}
|
||||
{#if $flag}
|
||||
<p class="text-light border-l-2 border-solid border-medium pl-4">
|
||||
You have flagged this content as offensive.
|
||||
<Anchor on:click={() => deleteReaction(flag)}>Unflag</Anchor>
|
||||
<Anchor on:click={() => deleteReaction($flag)}>Unflag</Anchor>
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-ellipsis overflow-hidden">
|
||||
@ -130,17 +126,17 @@
|
||||
<i
|
||||
class="fa-solid fa-reply cursor-pointer"
|
||||
on:click={startReply} />
|
||||
{note.children.length}
|
||||
{$replies?.length}
|
||||
</div>
|
||||
<div class={cx({'text-accent': like})}>
|
||||
<div class={cx({'text-accent': $like})}>
|
||||
<i
|
||||
class="fa-solid fa-heart cursor-pointer"
|
||||
on:click={() => like ? deleteReaction(like) : react("+")} />
|
||||
{uniqBy(prop('pubkey'), note.reactions.filter(whereEq({content: '+'}))).length}
|
||||
on:click={() => $like ? deleteReaction($like) : react("+")} />
|
||||
{$likes}
|
||||
</div>
|
||||
<div>
|
||||
<i class="fa-solid fa-flag cursor-pointer" on:click={() => react("-")} />
|
||||
{uniqBy(prop('pubkey'), note.reactions.filter(whereEq({content: '-'}))).length}
|
||||
{$flags}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@ -169,9 +165,9 @@
|
||||
{/if}
|
||||
|
||||
{#if depth > 0}
|
||||
{#each uniqBy(prop('id'), note.children) as child (child.id)}
|
||||
{#each ($replies || []) as r (r.id)}
|
||||
<div class="ml-5 border-l border-solid border-medium">
|
||||
<svelte:self note={child} depth={depth - 1} {invertColors} {anchorId} />
|
||||
<svelte:self note={r} depth={depth - 1} {invertColors} {anchorId} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
@ -1,26 +1,59 @@
|
||||
<script>
|
||||
import {liveQuery} from 'dexie'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {prop, identity, concat, uniqBy, groupBy} from 'ramda'
|
||||
import {createMap} from 'hurdak/lib/hurdak'
|
||||
import {findReply, findRoot} from 'src/nostr/tags'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {uniqBy, sortBy, reject, prop} from 'ramda'
|
||||
import {createScroller, getMuffleValue, threadify, notesListener} from "src/util/notes"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import relay from 'src/relay'
|
||||
import {modal} from "src/state/app"
|
||||
|
||||
export let filter
|
||||
export let shouldMuffle = false
|
||||
|
||||
const notes = liveQuery(() => relay.filterEvents(filter).toArray())
|
||||
const toThread = async notes => {
|
||||
const ancestorIds = concat(notes.map(findRoot), notes.map(findReply)).filter(identity)
|
||||
const ancestors = await relay.filterEvents({kinds: [1], ids: ancestorIds}).toArray()
|
||||
|
||||
const allNotes = uniqBy(prop('id'), notes.concat(ancestors))
|
||||
const notesById = createMap('id', allNotes)
|
||||
const notesByRoot = groupBy(
|
||||
n => {
|
||||
const rootId = findRoot(n)
|
||||
const parentId = findReply(n)
|
||||
|
||||
// Actually dereference the notes in case we weren't able to retrieve them
|
||||
if (notesById[rootId]) {
|
||||
return rootId
|
||||
}
|
||||
|
||||
if (notesById[parentId]) {
|
||||
return parentId
|
||||
}
|
||||
|
||||
return n.id
|
||||
},
|
||||
allNotes
|
||||
)
|
||||
|
||||
return Object.keys(notesByRoot).map(id => notesById[id])
|
||||
}
|
||||
|
||||
const notes = relay.lq(async () => {
|
||||
let data = relay.filterEvents(filter).limit(10)
|
||||
|
||||
if (shouldMuffle) {
|
||||
data = data
|
||||
}
|
||||
|
||||
return await toThread(await data.reverse().sortBy('created_at'))
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- <svelte:window on:scroll={scroller?.start} /> -->
|
||||
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each ($notes || []) as n (n.id)}
|
||||
<li>{n.content}</li>
|
||||
<!-- <li><Note interactive note={n} depth={2} /></li> -->
|
||||
<li><Note interactive note={n} depth={2} /></li>
|
||||
{:else}
|
||||
<li class="p-20 text-center" in:fly={{y: 20}}>No notes found.</li>
|
||||
{/each}
|
||||
|
@ -1,26 +1,38 @@
|
||||
import Dexie from 'dexie'
|
||||
import {defmulti} from 'hurdak/lib/hurdak'
|
||||
import {now, timedelta} from 'src/util/misc'
|
||||
import {filterTags} from 'src/nostr/tags'
|
||||
import {worker} from 'src/relay/worker'
|
||||
|
||||
export const db = new Dexie('coracle/relay')
|
||||
|
||||
db.version(1).stores({
|
||||
db.version(2).stores({
|
||||
events: '++id, pubkey, created_at, kind, content',
|
||||
users: '++pubkey, name, about',
|
||||
tags: 'type, value, event',
|
||||
tags: '++key, event, value',
|
||||
})
|
||||
|
||||
window.db = db
|
||||
|
||||
// Hooks
|
||||
|
||||
db.events.hook('creating', (id, {pubkey, tags}, t) => {
|
||||
// We can't return a promise, so use setTimeout instead
|
||||
setTimeout(async () => {
|
||||
const user = await db.users.where('pubkey').equals(pubkey).first()
|
||||
db.events.hook('creating', (id, e, t) => {
|
||||
setTimeout(() => {
|
||||
for (const tag of e.tags) {
|
||||
db.tags.put({
|
||||
id: [id, ...tag.slice(0, 2)].join(':'),
|
||||
event: id,
|
||||
type: tag[0],
|
||||
value: tag[1],
|
||||
relay: tag[2],
|
||||
mark: tag[3],
|
||||
})
|
||||
}
|
||||
|
||||
// Throttle updates for users
|
||||
if (!user || user.updated_at < now() - timedelta(1, 'hours')) {
|
||||
worker.post('user/update', user || {pubkey, updated_at: 0})
|
||||
if (e.kind === 5) {
|
||||
const eventIds = filterTags({tag: "e"}, e)
|
||||
|
||||
db.events.where('id').anyOf(eventIds).delete()
|
||||
db.tags.where('event').anyOf(eventIds).delete()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1,23 +1,83 @@
|
||||
import {liveQuery} from 'dexie'
|
||||
import {pluck, isNil} from 'ramda'
|
||||
import {ensurePlural, first} from 'hurdak/lib/hurdak'
|
||||
import {now, timedelta} from 'src/util/misc'
|
||||
import {db} from 'src/relay/db'
|
||||
import {worker} from 'src/relay/worker'
|
||||
import {ensurePlural} from 'hurdak/lib/hurdak'
|
||||
|
||||
const filterEvents = ({kinds, ids, authors, ...filter}) => {
|
||||
let t = db.events
|
||||
// Livequery appears to swallow errors
|
||||
const lq = f => liveQuery(async () => {
|
||||
try {
|
||||
return await f()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
if (kinds) {
|
||||
t = t.where('kind').anyOf(ensurePlural(kinds))
|
||||
const ensureContext = async e => {
|
||||
// We can't return a promise, so use setTimeout instead
|
||||
const user = await db.users.where('pubkey').equals(e.pubkey).first()
|
||||
|
||||
// Throttle updates for users
|
||||
if (!user || user.updated_at < now() - timedelta(1, 'hours')) {
|
||||
worker.post('user/update', user || {pubkey: e.pubkey, updated_at: 0})
|
||||
}
|
||||
|
||||
if (ids) {
|
||||
t = t.where('id').anyOf(ensurePlural(ids))
|
||||
}
|
||||
|
||||
if (authors) {
|
||||
t = t.where('pubkey').anyOf(ensurePlural(authors))
|
||||
}
|
||||
|
||||
return t
|
||||
// TODO optimize this like user above so we're not double-fetching
|
||||
worker.post('event/fetchContext', e)
|
||||
}
|
||||
|
||||
export default {db, worker, filterEvents}
|
||||
const prefilterEvents = filter => {
|
||||
if (filter.ids) {
|
||||
return db.events.where('id').anyOf(ensurePlural(filter.ids))
|
||||
}
|
||||
|
||||
if (filter.authors) {
|
||||
return db.events.where('pubkey').anyOf(ensurePlural(filter.authors))
|
||||
}
|
||||
|
||||
if (filter.kinds) {
|
||||
return db.events.where('kind').anyOf(ensurePlural(filter.kinds))
|
||||
}
|
||||
|
||||
return db.events
|
||||
}
|
||||
|
||||
const filterEvents = filter => {
|
||||
return prefilterEvents(filter)
|
||||
.filter(e => {
|
||||
if (filter.ids && !filter.ids.includes(e.id)) return false
|
||||
if (filter.authors && !filter.authors.includes(e.pubkey)) return false
|
||||
if (filter.kinds && !filter.kinds.includes(e.kind)) return false
|
||||
if (!isNil(filter.content) && filter.content !== e.content) return false
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const filterReplies = async (id, filter) => {
|
||||
const tags = db.tags.where('value').equals(id).filter(t => t.mark === 'reply')
|
||||
const ids = pluck('event', await tags.toArray())
|
||||
const replies = await filterEvents({...filter, kinds: [1], ids}).toArray()
|
||||
|
||||
return replies
|
||||
}
|
||||
|
||||
const filterReactions = async (id, filter) => {
|
||||
const tags = db.tags.where('value').equals(id).filter(t => t.mark === 'reply')
|
||||
const ids = pluck('event', await tags.toArray())
|
||||
const reactions = await filterEvents({...filter, kinds: [7], ids}).toArray()
|
||||
|
||||
return reactions
|
||||
}
|
||||
|
||||
const findReaction = async (id, filter) =>
|
||||
first(await filterReactions(id, filter))
|
||||
|
||||
const countReactions = async (id, filter) =>
|
||||
(await filterReactions(id, filter)).length
|
||||
|
||||
export default {
|
||||
db, worker, lq, ensureContext, filterEvents, filterReactions, countReactions,
|
||||
findReaction, filterReplies,
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script>
|
||||
import {writable} from 'svelte/store'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {timedelta} from 'src/util/misc'
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
@ -10,10 +9,7 @@
|
||||
|
||||
export let activeTab
|
||||
|
||||
const globalNotes = writable([])
|
||||
const followNotes = writable([])
|
||||
const authors = $user ? $user.petnames.map(t => t[1]) : []
|
||||
|
||||
const setActiveTab = tab => navigate(`/notes/${tab}`)
|
||||
</script>
|
||||
|
||||
@ -34,9 +30,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if activeTab === 'follows'}
|
||||
<Notes notes={followNotes} filter={{kinds: [1], authors}} shouldMuffle />
|
||||
<Notes filter={{kinds: [1], authors}} shouldMuffle />
|
||||
{:else}
|
||||
<Notes delta={timedelta(1, 'minutes')} notes={globalNotes} filter={{kinds: [1]}} shouldMuffle />
|
||||
<Notes filter={{kinds: [1]}} shouldMuffle />
|
||||
{/if}
|
||||
<div class="fixed bottom-0 right-0 p-8">
|
||||
<a
|
||||
|
@ -2,6 +2,7 @@ import {identity, isNil, uniqBy, last, without} from 'ramda'
|
||||
import {get} from 'svelte/store'
|
||||
import {first, defmulti} from "hurdak/lib/hurdak"
|
||||
import {user} from "src/state/user"
|
||||
import relay from 'src/relay'
|
||||
import {nostr, relays} from 'src/state/nostr'
|
||||
import {ensureAccounts} from 'src/state/app'
|
||||
|
||||
@ -34,7 +35,7 @@ dispatch.addMethod("account/update", async (topic, updates) => {
|
||||
user.set({...get(user), ...updates})
|
||||
|
||||
// Tell the network
|
||||
await nostr.publish(nostr.event(0, JSON.stringify(updates)))
|
||||
await relay.worker.post('event/publish', nostr.event(0, JSON.stringify(updates)))
|
||||
})
|
||||
|
||||
dispatch.addMethod("account/petnames", async (topic, petnames) => {
|
||||
@ -44,7 +45,7 @@ dispatch.addMethod("account/petnames", async (topic, petnames) => {
|
||||
user.set({...$user, petnames})
|
||||
|
||||
// Tell the network
|
||||
await nostr.publish(nostr.event(3, '', petnames))
|
||||
await relay.worker.post('event/publish', nostr.event(3, '', petnames))
|
||||
})
|
||||
|
||||
dispatch.addMethod("account/muffle", async (topic, muffle) => {
|
||||
@ -54,7 +55,7 @@ dispatch.addMethod("account/muffle", async (topic, muffle) => {
|
||||
user.set({...$user, muffle})
|
||||
|
||||
// Tell the network
|
||||
await nostr.publish(nostr.event(12165, '', muffle))
|
||||
await relay.worker.post('event/publish', nostr.event(12165, '', muffle))
|
||||
})
|
||||
|
||||
dispatch.addMethod("relay/join", async (topic, url) => {
|
||||
@ -74,7 +75,7 @@ dispatch.addMethod("relay/leave", (topic, url) => {
|
||||
dispatch.addMethod("room/create", async (topic, room) => {
|
||||
const event = nostr.event(40, JSON.stringify(room))
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -82,7 +83,7 @@ dispatch.addMethod("room/create", async (topic, room) => {
|
||||
dispatch.addMethod("room/update", async (topic, {id, ...room}) => {
|
||||
const event = nostr.event(41, JSON.stringify(room), [t("e", id)])
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -90,7 +91,7 @@ dispatch.addMethod("room/update", async (topic, {id, ...room}) => {
|
||||
dispatch.addMethod("message/create", async (topic, roomId, content) => {
|
||||
const event = nostr.event(42, content, [t("e", roomId, "root")])
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -98,7 +99,7 @@ dispatch.addMethod("message/create", async (topic, roomId, content) => {
|
||||
dispatch.addMethod("note/create", async (topic, content, tags=[]) => {
|
||||
const event = nostr.event(1, content, tags)
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -107,7 +108,7 @@ dispatch.addMethod("reaction/create", async (topic, content, e) => {
|
||||
const tags = copyTags(e, [t("p", e.pubkey), t("e", e.id, 'reply')])
|
||||
const event = nostr.event(7, content, tags)
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -116,7 +117,7 @@ dispatch.addMethod("reply/create", async (topic, content, e) => {
|
||||
const tags = copyTags(e, [t("p", e.pubkey), t("e", e.id, 'reply')])
|
||||
const event = nostr.event(1, content, tags)
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
@ -124,7 +125,7 @@ dispatch.addMethod("reply/create", async (topic, content, e) => {
|
||||
dispatch.addMethod("event/delete", async (topic, ids) => {
|
||||
const event = nostr.event(5, '', ids.map(id => t("e", id)))
|
||||
|
||||
await nostr.publish(event)
|
||||
await relay.worker.post('event/publish', event)
|
||||
|
||||
return event
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {writable} from "svelte/store"
|
||||
import {getLocalJson, setLocalJson} from "src/util/misc"
|
||||
import {nostr} from 'src/state/nostr'
|
||||
import relay from 'src/relay'
|
||||
|
||||
export const user = writable(getLocalJson("coracle/user"))
|
||||
|
||||
@ -10,8 +11,10 @@ user.subscribe($user => {
|
||||
// Keep nostr in sync
|
||||
if ($user?.privkey) {
|
||||
nostr.login($user.privkey)
|
||||
relay.worker.post('pool/setPrivateKey', $user.privkey)
|
||||
} else if ($user?.pubkey) {
|
||||
nostr.pubkeyLogin($user.pubkey)
|
||||
relay.worker.post('pool/setPublicKey', $user.pubkey)
|
||||
}
|
||||
|
||||
// Migrate data from old formats
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {relayPool} from 'nostr-tools'
|
||||
import {defmulti, noop, uuid} from 'hurdak/lib/hurdak'
|
||||
import {now, timedelta} from "src/util/misc"
|
||||
import {filterTags} from "src/nostr/tags"
|
||||
|
||||
// ============================================================================
|
||||
// Utils/config
|
||||
@ -66,17 +67,26 @@ onmessage.addMethod('pool/setPrivateKey', withPayload(privkey => {
|
||||
}))
|
||||
|
||||
onmessage.addMethod('pool/setPublicKey', withPayload(pubkey => {
|
||||
pool._privkey = pubkey
|
||||
// TODO fix this, it ain't gonna work
|
||||
pool.registerSigningFunction(async event => {
|
||||
const {sig} = await window.nostr.signEvent(event)
|
||||
|
||||
return sig
|
||||
})
|
||||
|
||||
pool._pubkey = pubkey
|
||||
}))
|
||||
|
||||
|
||||
onmessage.addMethod('event/publish', withPayload(event => {
|
||||
pool.publish(event)
|
||||
post('events/put', event)
|
||||
}))
|
||||
|
||||
onmessage.addMethod('user/update', withPayload(async user => {
|
||||
if (!user.pubkey) throw new Error("Invalid user")
|
||||
|
||||
req({
|
||||
const sub = req({
|
||||
filter: {kinds: [0], authors: [user.pubkey], since: user.updated_at},
|
||||
onEvent: e => {
|
||||
try {
|
||||
@ -86,7 +96,20 @@ onmessage.addMethod('user/update', withPayload(async user => {
|
||||
}
|
||||
},
|
||||
onEose: () => {
|
||||
sub.unsub()
|
||||
|
||||
post('users/put', {...user, updated_at: now()})
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
onmessage.addMethod('event/fetchContext', withPayload(async event => {
|
||||
const sub = req({
|
||||
filter: [
|
||||
{kinds: [5, 7], '#e': [event.id]},
|
||||
{kinds: [5], 'ids': filterTags({tag: "e"}, event)},
|
||||
],
|
||||
onEvent: e => post('events/put', e),
|
||||
onEose: () => sub.unsub(),
|
||||
})
|
||||
}))
|
||||
|
Loading…
Reference in New Issue
Block a user