mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add new thread view
This commit is contained in:
parent
fe25170bcf
commit
8648db3da3
@ -1,5 +1,6 @@
|
||||
Bugs
|
||||
|
||||
- [ ] Some threads aren't navigable, click handlers aren't firing. Like isn't working, but un-like is.
|
||||
- [ ] Permalink note detail (share/permalink button?). Permalinks don't work
|
||||
- [ ] uniq and sortBy are sprinkled all over the place, figure out a better solution
|
||||
- [ ] With link/image previews, remove the url from the note body if it's on a separate last line
|
||||
|
@ -103,7 +103,7 @@
|
||||
|
||||
<ul
|
||||
class="py-20 w-56 bg-dark fixed top-0 bottom-0 left-0 transition-all shadow-xl
|
||||
border-r border-medium text-white overflow-hidden"
|
||||
border-r border-medium text-white overflow-hidden z-10"
|
||||
class:-ml-56={!$menuIsOpen}
|
||||
>
|
||||
{#if $user}
|
||||
@ -164,7 +164,7 @@
|
||||
|
||||
<div
|
||||
class="fixed top-0 bg-dark flex justify-between items-center text-white w-full p-4
|
||||
border-b border-medium"
|
||||
border-b border-medium z-10"
|
||||
>
|
||||
<i class="fa-solid fa-bars fa-2xl cursor-pointer" bind:this={menuIcon} on:click={toggleMenu} />
|
||||
<Anchor external type="unstyled" href="https://github.com/staab/coracle">
|
||||
@ -173,7 +173,7 @@
|
||||
</div>
|
||||
|
||||
{#if $modal}
|
||||
<div class="fixed inset-0">
|
||||
<div class="fixed inset-0 z-10">
|
||||
<div
|
||||
class="absolute inset-0 opacity-75 bg-black cursor-pointer"
|
||||
transition:fade
|
||||
|
@ -5,8 +5,8 @@
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {Cursor, Listener, epoch, findReplyTo, channels} from 'src/state/nostr'
|
||||
import {createScroller, annotateNotes, modal} from "src/state/app"
|
||||
import {Cursor, Listener, epoch, findReply, channels} from 'src/state/nostr'
|
||||
import {createScroller, threadify, modal} from "src/state/app"
|
||||
|
||||
export let filter
|
||||
export let notes
|
||||
@ -19,14 +19,14 @@
|
||||
let loading = true
|
||||
|
||||
const addLikes = async likes => {
|
||||
const noteIds = likes.filter(e => e.content === '+').map(findReplyTo).filter(identity)
|
||||
const noteIds = likes.filter(e => e.content === '+').map(findReply).filter(identity)
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const chunk = await channels.getter.all({kinds: [1], ids: noteIds})
|
||||
const annotated = await annotateNotes(chunk, {showParents: true})
|
||||
const annotated = await threadify(chunk)
|
||||
|
||||
notes.update($notes => sortBy(n => -n.created_at, uniqBy(prop('id'), $notes.concat(annotated))))
|
||||
}
|
||||
@ -68,14 +68,7 @@
|
||||
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each $notes as n (n.id)}
|
||||
<li>
|
||||
<Note interactive note={n} />
|
||||
{#each n.replies as r (r.id)}
|
||||
<div class="ml-6 border-l border-solid border-medium">
|
||||
<Note interactive isReply note={r} />
|
||||
</div>
|
||||
{/each}
|
||||
</li>
|
||||
<li><Note interactive note={n} /></li>
|
||||
{:else}
|
||||
{#if loading}
|
||||
<li><Spinner /></li>
|
||||
|
@ -9,14 +9,13 @@
|
||||
import Preview from 'src/partials/Preview.svelte'
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import {dispatch} from "src/state/dispatch"
|
||||
import {findReplyTo} from "src/state/nostr"
|
||||
import {accounts, settings, modal} from "src/state/app"
|
||||
import {user} from "src/state/user"
|
||||
import {formatTimestamp} from 'src/util/misc'
|
||||
import UserBadge from "src/partials/UserBadge.svelte"
|
||||
|
||||
export let note
|
||||
export let isReply = false
|
||||
export let depth = 0
|
||||
export let showEntire = false
|
||||
export let interactive = false
|
||||
export let invertColors = false
|
||||
@ -25,12 +24,10 @@
|
||||
let like = null
|
||||
let flag = null
|
||||
let reply = null
|
||||
let parentId
|
||||
|
||||
$: {
|
||||
like = find(e => e.pubkey === $user?.pubkey && e.content === "+", note.reactions)
|
||||
flag = find(e => e.pubkey === $user?.pubkey && e.content === "-", note.reactions)
|
||||
parentId = findReplyTo(note)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@ -43,11 +40,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const showParent = async () => {
|
||||
|
||||
modal.set({note: {id: parentId}})
|
||||
}
|
||||
|
||||
const react = content => {
|
||||
if ($user) {
|
||||
dispatch('reaction/create', content, note)
|
||||
@ -110,11 +102,6 @@
|
||||
<p class="text-sm text-light">{formatTimestamp(note.created_at)}</p>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col gap-2">
|
||||
{#if parentId && !isReply}
|
||||
<small class="text-light">
|
||||
Reply to <Anchor on:click={showParent}>{parentId.slice(0, 8)}</Anchor>
|
||||
</small>
|
||||
{/if}
|
||||
{#if flag}
|
||||
<p class="text-light border-l-2 border-solid border-medium pl-4">
|
||||
You have flagged this content as offensive.
|
||||
@ -138,7 +125,7 @@
|
||||
<i
|
||||
class="fa-solid fa-reply cursor-pointer"
|
||||
on:click={startReply} />
|
||||
{note.replies.length}
|
||||
{note.children.length}
|
||||
</div>
|
||||
<div class={cx({'text-accent': like})}>
|
||||
<i
|
||||
@ -175,3 +162,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if depth > 0}
|
||||
{#each note.children as child (child.id)}
|
||||
<div class="ml-5 border-l border-solid border-medium">
|
||||
<svelte:self note={child} interactive depth={depth - 1} {invertColors} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
@ -1,22 +1,34 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {writable} from 'svelte/store'
|
||||
import {reverse} from 'ramda'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import {channels} from "src/state/nostr"
|
||||
import {notesListener, annotateNotes, modal} from "src/state/app"
|
||||
import {notesListener, threadify, modal} from "src/state/app"
|
||||
import {user} from "src/state/user"
|
||||
import Note from 'src/partials/Note.svelte'
|
||||
|
||||
export let note
|
||||
|
||||
const notes = writable([])
|
||||
let notes = writable([])
|
||||
let cursor
|
||||
let listener
|
||||
|
||||
const getAncestors = n => {
|
||||
const parents = []
|
||||
|
||||
while (n.parent) {
|
||||
parents.push(n.parent)
|
||||
n = n.parent
|
||||
}
|
||||
|
||||
return reverse(parents)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
channels.getter
|
||||
.all({kinds: [1, 5, 7], ids: [note.id]})
|
||||
.then(annotateNotes)
|
||||
.all({kinds: [1], ids: [note.id]})
|
||||
.then(threadify)
|
||||
.then($notes => {
|
||||
notes.set($notes)
|
||||
})
|
||||
@ -45,22 +57,15 @@
|
||||
|
||||
{#each $notes as note (note.id)}
|
||||
<div n:fly={{y: 20}}>
|
||||
<Note showEntire note={note} />
|
||||
{#each note.replies as r (r.id)}
|
||||
<div class="ml-4 border-l border-solid border-medium">
|
||||
<Note interactive invertColors isReply note={r} />
|
||||
{#each r.replies as r2 (r2.id)}
|
||||
<div class="ml-4 border-l border-solid border-medium">
|
||||
<Note interactive invertColors isReply note={r2} />
|
||||
{#each r2.replies as r3 (r3.id)}
|
||||
<div class="ml-4 border-l border-solid border-medium">
|
||||
<Note interactive invertColors isReply note={r3} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="relative">
|
||||
{#if note.parent}
|
||||
<div class="w-px bg-medium absolute h-full ml-5 -mr-5 mt-5" />
|
||||
{/if}
|
||||
{#each getAncestors(note) as ancestor (ancestor.id)}
|
||||
<Note interactive invertColors note={ancestor} />
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<Note showEntire invertColors depth={5} note={note} />
|
||||
</div>
|
||||
{:else}
|
||||
<Spinner />
|
||||
|
@ -2,10 +2,11 @@
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {uniqBy, sortBy, reject, prop} from 'ramda'
|
||||
import {pluralize} from 'hurdak/lib/hurdak'
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {Cursor, epoch} from 'src/state/nostr'
|
||||
import {createScroller, getMuffleValue, annotateNotes, notesListener, modal} from "src/state/app"
|
||||
import {Cursor, epoch, filterTags} from 'src/state/nostr'
|
||||
import {createScroller, getMuffleValue, threadify, combineThreads, notesListener, modal} from "src/state/app"
|
||||
|
||||
export let filter
|
||||
export let notes
|
||||
@ -18,6 +19,18 @@
|
||||
let interval
|
||||
let loading = true
|
||||
|
||||
const getRoot = n => {
|
||||
if (!n.parent) {
|
||||
return null
|
||||
}
|
||||
|
||||
while (n.parent) {
|
||||
n = n.parent
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
cursor = new Cursor(filter)
|
||||
listener = await notesListener(notes, [filter, {kinds: [5, 7]}], {shouldMuffle})
|
||||
@ -27,9 +40,14 @@
|
||||
chunk = reject(n => Math.random() > getMuffleValue(n.pubkey), chunk)
|
||||
}
|
||||
|
||||
const annotated = await annotateNotes(chunk, {showParents: true})
|
||||
// Remove anything that's an ancestor of something we've already seen
|
||||
const seen = $notes.flatMap(n => filterTags({tag: "e"}, n))
|
||||
|
||||
notes.update($notes => sortBy(n => -n.created_at, uniqBy(prop('id'), $notes.concat(annotated))))
|
||||
// Add chunk context and combine threads
|
||||
chunk = combineThreads(await threadify(reject(n => seen.includes(n.id), chunk)))
|
||||
|
||||
// Sort and deduplicate
|
||||
notes.set(sortBy(n => -n.created_at, uniqBy(prop('id'), $notes.concat(chunk))))
|
||||
})
|
||||
|
||||
// Track loading based on cursor cutoff date
|
||||
@ -64,13 +82,29 @@
|
||||
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each $notes as n (n.id)}
|
||||
{@const root = getRoot(n)}
|
||||
<li>
|
||||
<Note interactive note={n} />
|
||||
{#each n.replies as r (r.id)}
|
||||
<div class="ml-6 border-l border-solid border-medium">
|
||||
<Note interactive isReply note={r} />
|
||||
<div class="relative">
|
||||
{#if n.parent}
|
||||
<div class="w-px bg-medium absolute h-full ml-5 -mr-5 mt-5" />
|
||||
{/if}
|
||||
{#if root && root.id !== n.parent?.id}
|
||||
<Note interactive note={root} />
|
||||
{/if}
|
||||
{#if n.numberOfAncestors > 2}
|
||||
<div class="z-10 text-medium bg-black relative py-1 px-2" style="left: 10px" in:fly={{y: 20}}>
|
||||
<i class="fa-solid fa-ellipsis-v" />
|
||||
<span class="pl-2 text-light opacity-75">
|
||||
{n.numberOfAncestors - 2} other {pluralize(n.numberOfAncestors - 2, 'note')}
|
||||
in this conversation
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if n.parent}
|
||||
<Note interactive depth={0} note={n.parent} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<Note interactive depth={1} note={n} />
|
||||
</li>
|
||||
{:else}
|
||||
{#if loading}
|
||||
|
@ -26,7 +26,7 @@
|
||||
</script>
|
||||
|
||||
{#if preview}
|
||||
<div in:slide>
|
||||
<div in:slide class="max-w-sm">
|
||||
<Anchor
|
||||
external
|
||||
href={url}
|
||||
|
@ -2,7 +2,7 @@
|
||||
export let user
|
||||
</script>
|
||||
|
||||
<a href={`/users/${user.pubkey}/notes`} class="flex gap-2 items-center">
|
||||
<a href={`/users/${user.pubkey}/notes`} class="flex gap-2 items-center relative z-10">
|
||||
<div
|
||||
class="overflow-hidden w-4 h-4 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
|
||||
style="background-image: url({user.picture})" />
|
||||
|
@ -10,7 +10,7 @@
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {relays, Cursor} from "src/state/nostr"
|
||||
import {user} from "src/state/user"
|
||||
import {createScroller, ensureAccounts, accounts, annotateNotes, modal} from "src/state/app"
|
||||
import {createScroller, ensureAccounts, accounts, threadify, modal} from "src/state/app"
|
||||
|
||||
export let type
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
data.set(Object.values($accounts))
|
||||
} else {
|
||||
const annotated = await annotateNotes(chunk)
|
||||
const annotated = await threadify(chunk)
|
||||
|
||||
data.update($data => uniqBy(prop('id'), $data.concat(annotated)))
|
||||
}
|
||||
@ -108,11 +108,6 @@
|
||||
{#each (results || []) as e (e.id)}
|
||||
<li in:fly={{y: 20}}>
|
||||
<Note interactive note={e} />
|
||||
{#each e.replies as r (r.id)}
|
||||
<div class="ml-6 border-l border-solid border-medium">
|
||||
<Note interactive isReply note={r} />
|
||||
</div>
|
||||
{/each}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
177
src/state/app.js
177
src/state/app.js
@ -1,14 +1,12 @@
|
||||
import {when, prop, identity, whereEq, reverse, uniq, sortBy, uniqBy, find, last, pluck, groupBy} from 'ramda'
|
||||
import {identity, uniq, uniqBy, prop, reject, groupBy, find, last, pluck} from 'ramda'
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {writable, get} from 'svelte/store'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {globalHistory} from "svelte-routing/src/history"
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {switcherFn, createMap} from 'hurdak/lib/hurdak'
|
||||
import {getLocalJson, setLocalJson, now, timedelta, sleep} from "src/util/misc"
|
||||
import {user} from 'src/state/user'
|
||||
import {epoch, filterMatches, Listener, channels, relays, findReplyTo} from 'src/state/nostr'
|
||||
|
||||
// ws://localhost:7000
|
||||
import {epoch, filterTags, filterMatches, Listener, channels, relays, findReply} from 'src/state/nostr'
|
||||
|
||||
export const modal = {
|
||||
subscribe: cb => {
|
||||
@ -131,103 +129,132 @@ export const getMuffleValue = pubkey => {
|
||||
|
||||
// Notes
|
||||
|
||||
export const annotateNotes = async (chunk, {showParents = false} = {}) => {
|
||||
const parentIds = chunk.map(findReplyTo).filter(identity)
|
||||
|
||||
if (showParents && parentIds.length) {
|
||||
// Find parents of replies to provide context
|
||||
const parents = await channels.getter.all({
|
||||
kinds: [1],
|
||||
ids: parentIds,
|
||||
})
|
||||
|
||||
// Remove replies, show parents instead
|
||||
chunk = parents
|
||||
.concat(chunk.filter(e => !find(whereEq({id: findReplyTo(e)}), parents)))
|
||||
export const threadify = async notes => {
|
||||
if (notes.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
chunk = uniqBy(prop('id'), chunk)
|
||||
const ancestorIds = filterTags({tag: 'e'}, notes)
|
||||
const filters = [{kinds: [1, 7], '#e': pluck('id', notes)}]
|
||||
|
||||
if (chunk.length === 0) {
|
||||
return chunk
|
||||
if (ancestorIds.length > 0) {
|
||||
filters.push({kinds: [1], ids: ancestorIds})
|
||||
filters.push({kinds: [7], '#e': ancestorIds})
|
||||
}
|
||||
|
||||
const replies = await channels.getter.all({
|
||||
kinds: [1],
|
||||
'#e': pluck('id', chunk),
|
||||
})
|
||||
const events = uniqBy(prop('id'), await channels.getter.all(filters))
|
||||
|
||||
const reactions = await channels.getter.all({
|
||||
kinds: [7],
|
||||
'#e': pluck('id', chunk.concat(replies)),
|
||||
})
|
||||
await ensureAccounts(uniq(pluck('pubkey', notes.concat(events))))
|
||||
|
||||
const repliesById = groupBy(findReplyTo, replies)
|
||||
const reactionsById = groupBy(findReplyTo, reactions)
|
||||
const $accounts = get(accounts)
|
||||
const eventsById = createMap('id', events)
|
||||
const getChildren = (kind, n) =>
|
||||
events.filter(e => e.kind === kind && findReply(e) === n.id)
|
||||
|
||||
await ensureAccounts(uniq(pluck('pubkey', chunk.concat(replies).concat(reactions))))
|
||||
const annotate = (n, includeParent) => {
|
||||
if (!n) {
|
||||
return null
|
||||
}
|
||||
|
||||
const annotated = {
|
||||
...n,
|
||||
numberOfAncestors: n.tags.filter(([x]) => x === 'e').length,
|
||||
children: getChildren(1, n).map(child => annotate(child, false)),
|
||||
reactions: getChildren(7, n),
|
||||
user: $accounts[n.pubkey],
|
||||
}
|
||||
|
||||
if (includeParent) {
|
||||
annotated.parent = annotate(eventsById[findReply(n)], true)
|
||||
}
|
||||
|
||||
return annotated
|
||||
}
|
||||
|
||||
// Add parent/children/reactions
|
||||
return notes.map(n => annotate(n, true))
|
||||
}
|
||||
|
||||
export const combineThreads = notes => {
|
||||
const notesById = createMap('id', notes.concat(pluck('parent', notes)).filter(identity))
|
||||
const parentIds = notes.map(n => n.parent?.id).filter(identity)
|
||||
|
||||
// Group by parent, but filter out notes alredy represented in the parents list to
|
||||
// avoid showing the same note twice. This privileges leaf nodes over root nodes.
|
||||
const notesByParent = groupBy(
|
||||
n => n.parent?.id || n.id,
|
||||
reject(n => parentIds.includes(n.id), notes)
|
||||
)
|
||||
|
||||
return Object.keys(notesByParent).map(id => notesById[id])
|
||||
}
|
||||
|
||||
export const annotateNewNote = async note => {
|
||||
await ensureAccounts([note.pubkey])
|
||||
|
||||
const $accounts = get(accounts)
|
||||
|
||||
const annotate = e => ({
|
||||
...e,
|
||||
user: $accounts[e.pubkey],
|
||||
replies: uniqBy(prop('id'), (repliesById[e.id] || []).map(reply => annotate(reply))),
|
||||
reactions: uniqBy(prop('id'), (reactionsById[e.id] || []).map(reaction => annotate(reaction))),
|
||||
})
|
||||
|
||||
return reverse(sortBy(prop('created'), chunk.map(annotate)))
|
||||
return {
|
||||
...note,
|
||||
user: $accounts[note.pubkey],
|
||||
numberOfAncestors: note.tags.filter(([x]) => x === 'e').length,
|
||||
children: [],
|
||||
reactions: [],
|
||||
}
|
||||
}
|
||||
|
||||
export const notesListener = (notes, filter, {shouldMuffle = false} = {}) => {
|
||||
const updateNote = (id, f) =>
|
||||
notes.update($notes =>
|
||||
$notes
|
||||
.map(n => {
|
||||
if (n.id === id) {
|
||||
return f(n)
|
||||
}
|
||||
const updateNote = (note, id, f) => {
|
||||
if (note.id === id) {
|
||||
return f(note)
|
||||
}
|
||||
|
||||
return {...n, replies: n.replies.map(when(whereEq({id}), f))}
|
||||
})
|
||||
)
|
||||
return {
|
||||
...note,
|
||||
parent: note.parent ? updateNote(note.parent, id, f) : null,
|
||||
children: note.children.map(n => updateNote(n, id, f)),
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNotes = ($notes, ids) =>
|
||||
$notes
|
||||
.filter(e => !ids.includes(e.id))
|
||||
.map(n => ({
|
||||
...n,
|
||||
replies: deleteNotes(n.replies, ids),
|
||||
reactions: n.reactions.filter(e => !ids.includes(e.id)),
|
||||
}))
|
||||
const updateNotes = (id, f) =>
|
||||
notes.update($notes => $notes.map(n => updateNote(n, id, f)))
|
||||
|
||||
return new Listener(filter, e => switcherFn(e.kind, {
|
||||
const deleteNote = (note, ids, deleted_at) => {
|
||||
if (ids.includes(note.id)) {
|
||||
return {...note, deleted_at}
|
||||
}
|
||||
|
||||
return {
|
||||
...note,
|
||||
parent: note.parent ? deleteNote(note.parent, ids, deleted_at) : null,
|
||||
children: note.children.map(n => deleteNote(n, ids, deleted_at)),
|
||||
reactions: note.reactions.filter(e => !ids.includes(e.id)),
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNotes = (ids, t) =>
|
||||
notes.update($notes => $notes.map(n => deleteNote(n, ids, t)))
|
||||
|
||||
return new Listener(filter, e => console.log(e) || switcherFn(e.kind, {
|
||||
1: async () => {
|
||||
const id = findReplyTo(e)
|
||||
|
||||
if (shouldMuffle && Math.random() > getMuffleValue(e.pubkey)) {
|
||||
return
|
||||
}
|
||||
const id = findReply(e)
|
||||
const muffle = shouldMuffle && Math.random() > getMuffleValue(e.pubkey)
|
||||
|
||||
if (id) {
|
||||
const [reply] = await annotateNotes([e])
|
||||
const note = await annotateNewNote(e)
|
||||
|
||||
updateNote(id, n => ({...n, replies: n.replies.concat(reply)}))
|
||||
} else if (filterMatches(filter, e)) {
|
||||
const [note] = await annotateNotes([e])
|
||||
updateNotes(id, n => ({...n, children: n.children.concat(note)}))
|
||||
} else if (!muffle && filterMatches(filter, e)) {
|
||||
const [note] = await threadify([e])
|
||||
|
||||
notes.update($notes => uniqBy(prop('id'), [note].concat($notes)))
|
||||
notes.update($notes => [note].concat($notes))
|
||||
}
|
||||
},
|
||||
5: () => {
|
||||
const ids = e.tags.map(t => t[1])
|
||||
|
||||
notes.update($notes => deleteNotes($notes, ids))
|
||||
deleteNotes(e.tags.map(t => t[1]), e.created_at)
|
||||
},
|
||||
7: () => {
|
||||
const id = findReplyTo(e)
|
||||
|
||||
updateNote(id, n => ({...n, reactions: n.reactions.concat(e)}))
|
||||
updateNotes(findReply(e), n => ({...n, reactions: n.reactions.concat(e)}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -27,9 +27,12 @@ export const filterTags = (where, events) =>
|
||||
export const findTag = (where, events) => first(filterTags(where, events))
|
||||
|
||||
// Support the deprecated version where tags are marked as replies
|
||||
export const findReplyTo = e =>
|
||||
export const findReply = e =>
|
||||
findTag({tag: "e", type: "reply"}, e) || findTag({tag: "e"}, e)
|
||||
|
||||
export const findRoot = e =>
|
||||
findTag({tag: "e", type: "root"}, e) || findTag({tag: "e"}, e)
|
||||
|
||||
export const filterMatches = (filter, e) => {
|
||||
return Boolean(find(
|
||||
f => {
|
||||
@ -99,7 +102,7 @@ export class Channel {
|
||||
r => {
|
||||
sub.unsub()
|
||||
|
||||
resolve(result)
|
||||
resolve(uniqBy(prop('id'), result))
|
||||
},
|
||||
)
|
||||
})
|
||||
@ -198,8 +201,11 @@ export class Listener {
|
||||
this.sub = await channels.listener.sub(
|
||||
filter.map(f => ({since, ...f})),
|
||||
e => {
|
||||
this.since = e.created_at
|
||||
this.onEvent(e)
|
||||
// Not sure why since filter isn't working here, it's just slightly off
|
||||
if (e.created_at >= since) {
|
||||
this.since = e.created_at + 1
|
||||
this.onEvent(e)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user