mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add mentions when composing a note or reply, tweak some feed timings
This commit is contained in:
parent
d0d3844ac2
commit
9dc679a944
10
README.md
10
README.md
@ -16,8 +16,7 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
- [x] Notifications
|
||||
- [x] Link previews
|
||||
- [x] Add notes, follows, likes tab to profile
|
||||
- [ ] Show relative dates
|
||||
- [ ] Mentions - render done, now reference in compose
|
||||
- [x] Mentions
|
||||
- [ ] Image uploads
|
||||
- [ ] An actual readme
|
||||
- [ ] Server discovery and relay publishing - https://github.com/nostr-protocol/nips/pull/32/files
|
||||
@ -38,6 +37,13 @@ If you like Coracle and want to support its development, you can donate sats via
|
||||
|
||||
# Changelog
|
||||
|
||||
## 0.2.6
|
||||
|
||||
- [x] Add support for at-mentions
|
||||
- [x] Improve cleanup on logout
|
||||
- [x] Move add note button to be available everywhere
|
||||
- [x] Fix reporting relay along with tags
|
||||
|
||||
## 0.2.5
|
||||
|
||||
- [x] Batch load context for feeds
|
||||
|
@ -22,6 +22,7 @@
|
||||
import Alerts from "src/routes/Alerts.svelte"
|
||||
import Notes from "src/routes/Notes.svelte"
|
||||
import Login from "src/routes/Login.svelte"
|
||||
import Logout from "src/routes/Logout.svelte"
|
||||
import Profile from "src/routes/Profile.svelte"
|
||||
import Settings from "src/routes/Settings.svelte"
|
||||
import Keys from "src/routes/Keys.svelte"
|
||||
@ -45,26 +46,6 @@
|
||||
let suspendedSubs = []
|
||||
let mostRecentAlert = $alerts.since
|
||||
|
||||
const logout = async () => {
|
||||
const $connections = get(connections)
|
||||
const $settings = get(settings)
|
||||
|
||||
localStorage.clear()
|
||||
|
||||
// Keep relays around
|
||||
await relay.db.events.clear()
|
||||
await relay.db.tags.clear()
|
||||
|
||||
// Remember the user's relay selection and settings
|
||||
connections.set($connections)
|
||||
settings.set($settings)
|
||||
|
||||
// do a hard refresh so everything gets totally cleared
|
||||
setTimeout(() => {
|
||||
window.location = '/login'
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Close menu on click outside
|
||||
document.querySelector("html").addEventListener("click", e => {
|
||||
@ -148,6 +129,7 @@
|
||||
<Route path="/profile" component={Profile} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/logout" component={Logout} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</div>
|
||||
|
||||
@ -202,7 +184,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="cursor-pointer">
|
||||
<a class="block px-4 py-2 hover:bg-accent transition-all" on:click={logout}>
|
||||
<a class="block px-4 py-2 hover:bg-accent transition-all" href="/logout">
|
||||
<i class="fa-solid fa-right-from-bracket mr-2" /> Logout
|
||||
</a>
|
||||
</li>
|
||||
@ -228,6 +210,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $user}
|
||||
<div class="fixed bottom-0 right-0 m-8">
|
||||
<a
|
||||
href="/notes/new"
|
||||
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
|
||||
items-center border border-dark shadow-2xl cursor-pointer">
|
||||
<span class="fa-sold fa-plus fa-2xl" />
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $modal}
|
||||
<div class="fixed inset-0 z-10">
|
||||
<div
|
||||
|
@ -3,8 +3,17 @@
|
||||
import {killEvent} from 'src/util/html'
|
||||
|
||||
export let person
|
||||
export let inert = false
|
||||
</script>
|
||||
|
||||
{#if inert}
|
||||
<span 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({person.picture})" />
|
||||
<span class="text-lg font-bold">{person.name || person.pubkey.slice(0, 8)}</span>
|
||||
</span>
|
||||
{:else}
|
||||
<Link
|
||||
to={`/people/${person.pubkey}/notes`}
|
||||
class="flex gap-2 items-center relative z-10"
|
||||
@ -14,3 +23,4 @@
|
||||
style="background-image: url({person.picture})" />
|
||||
<span class="text-lg font-bold">{person.name || person.pubkey.slice(0, 8)}</span>
|
||||
</Link>
|
||||
{/if}
|
||||
|
@ -3,10 +3,12 @@
|
||||
import {switcher} from "hurdak/lib/hurdak"
|
||||
|
||||
export let theme = "default"
|
||||
export let disabled = false
|
||||
|
||||
const className = cx(
|
||||
$$props.class,
|
||||
"py-2 px-4 rounded cursor-pointer",
|
||||
{'text-light': disabled},
|
||||
switcher(theme, {
|
||||
default: "bg-white text-accent",
|
||||
accent: "text-white bg-accent",
|
||||
|
164
src/partials/Compose.svelte
Normal file
164
src/partials/Compose.svelte
Normal file
@ -0,0 +1,164 @@
|
||||
<script>
|
||||
import {prop, reject, sortBy, last} from 'ramda'
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {fromParentOffset} from "src/util/html"
|
||||
import Badge from "src/partials/Badge.svelte"
|
||||
import {people} from "src/relay"
|
||||
|
||||
export let onSubmit
|
||||
|
||||
let index = 0
|
||||
let mentions = []
|
||||
let suggestions = []
|
||||
let input = null
|
||||
let content = ''
|
||||
let search = fuzzy(
|
||||
Object.values($people).filter(prop('name')),
|
||||
{keys: ["name", "pubkey"]}
|
||||
)
|
||||
|
||||
const getText = () => {
|
||||
const selection = document.getSelection()
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
range.setStartBefore(input)
|
||||
|
||||
const text = range.cloneContents().textContent
|
||||
|
||||
range.collapse()
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
const getWord = () => {
|
||||
return last(getText().split(/[\s\u200B]+/))
|
||||
}
|
||||
|
||||
const pickSuggestion = ({name, pubkey}) => {
|
||||
const text = getText()
|
||||
const word = getWord()
|
||||
const selection = document.getSelection()
|
||||
const {focusNode, focusOffset} = selection
|
||||
const at = document.createTextNode("@")
|
||||
const span = document.createElement('span')
|
||||
|
||||
// Space includes a zero-width space to avoid having the cursor end up inside
|
||||
// mention span on backspace, and a space for convenience in composition.
|
||||
const space = document.createTextNode("\u200B\u00a0")
|
||||
|
||||
span.classList.add('underline')
|
||||
span.innerText = name
|
||||
|
||||
// Remove our partial mention text
|
||||
selection.setBaseAndExtent(...fromParentOffset(input, text.length - word.length), focusNode, focusOffset)
|
||||
selection.deleteFromDocument()
|
||||
|
||||
// Add the at sign, decorated mention text, and a trailing space
|
||||
selection.getRangeAt(0).insertNode(at)
|
||||
selection.collapse(at, 1)
|
||||
selection.getRangeAt(0).insertNode(span)
|
||||
selection.collapse(span.nextSibling, 0)
|
||||
selection.getRangeAt(0).insertNode(space)
|
||||
selection.collapse(space, 2)
|
||||
|
||||
mentions.push({
|
||||
name,
|
||||
pubkey,
|
||||
length: name.length + 1,
|
||||
end: getText().length - 2,
|
||||
})
|
||||
|
||||
index = 0
|
||||
suggestions = []
|
||||
}
|
||||
|
||||
const onKeyDown = e => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
return onSubmit()
|
||||
}
|
||||
|
||||
if (['Enter', 'Tab', 'ArrowUp', 'ArrowDown', ' '].includes(e.key) && suggestions[index]) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyUp = e => {
|
||||
if (['Enter', 'Tab', ' '].includes(e.key) && suggestions[index]) {
|
||||
pickSuggestion(suggestions[index])
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp' && suggestions[index - 1]) {
|
||||
index -= 1
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown' && suggestions[index + 1]) {
|
||||
index += 1
|
||||
}
|
||||
|
||||
if (input.innerText !== content) {
|
||||
const text = getText()
|
||||
const word = getWord()
|
||||
|
||||
if (!text.match(/\s$/) && word.startsWith('@')) {
|
||||
suggestions = search(word.slice(1)).slice(0, 3)
|
||||
} else {
|
||||
index = 0
|
||||
suggestions = []
|
||||
}
|
||||
|
||||
if (input.innerText.length < content.length) {
|
||||
const delta = content.length - input.innerText.length
|
||||
const text = getText()
|
||||
|
||||
for (const mention of mentions) {
|
||||
if (mention.end - mention.length > text.length) {
|
||||
mention.end -= delta
|
||||
} else if (mention.end > text.length) {
|
||||
mention.invalid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content = input.innerText
|
||||
}
|
||||
}
|
||||
|
||||
export const parse = () => {
|
||||
// Interpolate mentions
|
||||
let offset = 0
|
||||
const validMentions = sortBy(prop('end'), reject(prop('invalid'), mentions))
|
||||
for (const [i, {end, length}] of validMentions.entries()) {
|
||||
const offsetEnd = end - offset
|
||||
const start = offsetEnd - length
|
||||
const tag = `#[${i}]`
|
||||
|
||||
content = content.slice(0, start) + tag + content.slice(offsetEnd)
|
||||
offset += length - tag.length
|
||||
}
|
||||
|
||||
// Remove our zero-length spaces
|
||||
content = content.replace(/\u200B/g, '')
|
||||
|
||||
return {content, mentions: validMentions.map(prop('pubkey'))}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
<div
|
||||
class="text-white w-full outline-0 p-2"
|
||||
autofocus
|
||||
contenteditable
|
||||
bind:this={input}
|
||||
on:keydown={onKeyDown}
|
||||
on:keyup={onKeyUp} />
|
||||
<slot name="addon" />
|
||||
</div>
|
||||
{#each suggestions as person, i (person.pubkey)}
|
||||
<div
|
||||
class="py-2 px-4 cursor-pointer"
|
||||
class:bg-black={index !== i}
|
||||
class:bg-dark={index === i}
|
||||
on:click={() => pickSuggestion(person)}>
|
||||
<Badge inert {person} />
|
||||
</div>
|
||||
{/each}
|
@ -12,6 +12,7 @@
|
||||
import {settings, modal} from "src/state/app"
|
||||
import {formatTimestamp} from 'src/util/misc'
|
||||
import Badge from "src/partials/Badge.svelte"
|
||||
import Compose from "src/partials/Compose.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import relay, {user} from 'src/relay'
|
||||
|
||||
@ -76,26 +77,21 @@
|
||||
|
||||
const startReply = () => {
|
||||
if ($user) {
|
||||
reply = reply || ''
|
||||
reply = reply || true
|
||||
} else {
|
||||
navigate('/login')
|
||||
}
|
||||
}
|
||||
|
||||
const sendReply = () => {
|
||||
if (reply) {
|
||||
relay.cmd.createReply(reply, note)
|
||||
const {content, mentions} = reply.parse()
|
||||
|
||||
if (content) {
|
||||
relay.cmd.createReply(note, content, mentions)
|
||||
|
||||
reply = null
|
||||
}
|
||||
}
|
||||
|
||||
const onReplyKeyDown = e => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
sendReply()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:body
|
||||
@ -160,24 +156,19 @@
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{#if reply !== null}
|
||||
{#if reply}
|
||||
<div
|
||||
class="note-reply flex bg-medium border-medium border border-solid"
|
||||
class="note-reply bg-medium border-medium border border-solid"
|
||||
transition:slide>
|
||||
<textarea
|
||||
rows="4"
|
||||
autofocus
|
||||
placeholder="Type something..."
|
||||
bind:value={reply}
|
||||
on:keydown={onReplyKeyDown}
|
||||
class="w-full p-2 text-white bg-medium
|
||||
placeholder:text-light outline-0 resize-none" />
|
||||
<div
|
||||
on:click={sendReply}
|
||||
class="flex flex-col py-8 p-4 justify-center gap-2 border-l border-solid border-dark
|
||||
hover:bg-accent transition-all cursor-pointer text-white ">
|
||||
<i class="fa-solid fa-paper-plane fa-xl" />
|
||||
</div>
|
||||
<Compose bind:this={reply} onSubmit={sendReply}>
|
||||
<div
|
||||
slot="addon"
|
||||
on:click={sendReply}
|
||||
class="flex flex-col py-8 p-4 justify-center gap-2 border-l border-solid border-dark
|
||||
hover:bg-accent transition-all cursor-pointer text-white ">
|
||||
<i class="fa-solid fa-paper-plane fa-xl" />
|
||||
</div>
|
||||
</Compose>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import {onMount} from 'svelte'
|
||||
import {slide} from 'svelte/transition'
|
||||
import {quantify} from 'hurdak/lib/hurdak'
|
||||
import {createScroller, now} from 'src/util/misc'
|
||||
import {createScroller, sleep, now} from 'src/util/misc'
|
||||
import {findReply} from 'src/util/nostr'
|
||||
import Spinner from 'src/partials/Spinner.svelte'
|
||||
import Note from "src/partials/Note.svelte"
|
||||
@ -13,12 +13,12 @@
|
||||
export let queryNotes
|
||||
|
||||
const notes = relay.lq(async () => {
|
||||
const notes = await queryNotes()
|
||||
const annotated = await relay.annotateChunk(notes)
|
||||
// Hacky way to wait for the loader to adjust the cursor so we have a nonzero duration
|
||||
await sleep(100)
|
||||
|
||||
return sortBy(
|
||||
e => -pluck('created_at', e.replies).concat(e.created_at).reduce((a, b) => Math.max(a, b)),
|
||||
annotated
|
||||
await relay.annotateChunk(await queryNotes())
|
||||
)
|
||||
})
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#if newNotesLength > 0}
|
||||
<div
|
||||
transition:slide
|
||||
in:slide
|
||||
class="mb-2 cursor-pointer text-center underline text-light"
|
||||
on:click={() => { until = now() }}>
|
||||
Load {quantify(newNotesLength, 'new note')}
|
||||
|
@ -2,6 +2,7 @@
|
||||
import cx from "classnames"
|
||||
|
||||
export let value
|
||||
export let element = null
|
||||
|
||||
const className = cx(
|
||||
$$props.class,
|
||||
@ -10,4 +11,6 @@
|
||||
)
|
||||
</script>
|
||||
|
||||
<textarea {...$$props} class={className} bind:value />
|
||||
<svelte:options accessors />
|
||||
|
||||
<textarea {...$$props} class={className} bind:this={element} bind:value on:keydown on:keypress />
|
||||
|
@ -25,13 +25,18 @@ const updateRoom = ({id, ...room}) => publishEvent(41, JSON.stringify(room), [t(
|
||||
|
||||
const createMessage = (roomId, content) => publishEvent(42, content, [t("e", roomId, "root")])
|
||||
|
||||
const createNote = (content, tags=[]) => publishEvent(1, content, tags)
|
||||
const createNote = (content, mentions = []) => publishEvent(1, content, mentions.map(p => t("p", p)))
|
||||
|
||||
const createReaction = (content, e) =>
|
||||
publishEvent(7, content, copyTags(e, [t("p", e.pubkey), t("e", e.id, 'reply')]))
|
||||
|
||||
const createReply = (content, e) =>
|
||||
publishEvent(1, content, copyTags(e, [t("p", e.pubkey), t("e", e.id, 'reply')]))
|
||||
const createReply = (e, content, mentions = []) => {
|
||||
const tags = mentions.map(p => t("p", p)).concat(
|
||||
copyTags(e, [t("p", e.pubkey), t("e", e.id, 'reply')])
|
||||
)
|
||||
|
||||
return publishEvent(1, content, tags)
|
||||
}
|
||||
|
||||
const deleteEvent = ids => publishEvent(5, '', ids.map(id => t("e", id)))
|
||||
|
||||
@ -48,7 +53,7 @@ const copyTags = (e, newTags = []) => {
|
||||
}
|
||||
|
||||
export const t = (type, content, marker) => {
|
||||
const tag = [type, content, first(Object.keys(relay.pool.getRelays()))]
|
||||
const tag = [type, content, first(relay.pool.getRelays())]
|
||||
|
||||
if (!isNil(marker)) {
|
||||
tag.push(marker)
|
||||
|
@ -105,7 +105,9 @@ db.events.process = async events => {
|
||||
}
|
||||
|
||||
// On initial load, delete old event data
|
||||
const threshold = now() - timedelta(1, 'days')
|
||||
setTimeout(() => {
|
||||
const threshold = now() - timedelta(1, 'days')
|
||||
|
||||
db.events.where('loaded_at').below(threshold).delete()
|
||||
db.tags.where('loaded_at').below(threshold).delete()
|
||||
db.events.where('loaded_at').below(threshold).delete()
|
||||
db.tags.where('loaded_at').below(threshold).delete()
|
||||
}, timedelta(10, 'seconds'))
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {fade} from 'svelte/transition'
|
||||
import {fade, fly} from 'svelte/transition'
|
||||
import {navigate} from 'svelte-routing'
|
||||
import {generatePrivateKey, getPublicKey} from 'nostr-tools'
|
||||
import {copyToClipboard} from "src/util/html"
|
||||
@ -59,7 +59,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center p-12">
|
||||
<div class="flex justify-center p-12" in:fly={{y: 20}}>
|
||||
<div class="flex flex-col gap-4 max-w-2xl">
|
||||
<div class="flex justify-center items-center flex-col mb-4">
|
||||
<h1 class="staatliches text-6xl">Welcome!</h1>
|
||||
|
29
src/routes/Logout.svelte
Normal file
29
src/routes/Logout.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
import {get} from 'svelte/store'
|
||||
import {fly} from 'svelte/transition'
|
||||
import {settings} from "src/state/app"
|
||||
import relay, {connections} from 'src/relay'
|
||||
|
||||
setTimeout(async () => {
|
||||
const $connections = get(connections)
|
||||
const $settings = get(settings)
|
||||
|
||||
// Clear localstorage
|
||||
localStorage.clear()
|
||||
|
||||
// Keep relays around, but delete events/tags
|
||||
await relay.db.events.clear()
|
||||
await relay.db.tags.clear()
|
||||
|
||||
// Remember the user's relay selection and settings
|
||||
connections.set($connections)
|
||||
settings.set($settings)
|
||||
|
||||
// do a hard refresh so everything gets totally cleared
|
||||
window.location = '/login'
|
||||
}, 300)
|
||||
</script>
|
||||
|
||||
<div class="max-w-xl m-auto text-center py-20" in:fly={{y:20}}>
|
||||
Clearing your local database...
|
||||
</div>
|
@ -2,21 +2,23 @@
|
||||
import {onMount} from "svelte"
|
||||
import {fly} from 'svelte/transition'
|
||||
import {navigate} from "svelte-routing"
|
||||
import Textarea from "src/partials/Textarea.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Compose from "src/partials/Compose.svelte"
|
||||
import toast from "src/state/toast"
|
||||
import relay, {user} from "src/relay"
|
||||
|
||||
let values = {}
|
||||
let input = null
|
||||
|
||||
const submit = async e => {
|
||||
e.preventDefault()
|
||||
const onSubmit = async e => {
|
||||
const {content, mentions} = input.parse()
|
||||
|
||||
await relay.cmd.createNote(values.content)
|
||||
if (content) {
|
||||
await relay.cmd.createNote(content, mentions)
|
||||
|
||||
toast.show("info", `Your note has been created!`)
|
||||
toast.show("info", `Your note has been created!`)
|
||||
|
||||
history.back()
|
||||
history.back()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@ -27,15 +29,17 @@
|
||||
</script>
|
||||
|
||||
<div class="m-auto">
|
||||
<form on:submit={submit} class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
|
||||
<form on:submit|preventDefault={onSubmit} class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
|
||||
<div class="flex flex-col gap-4 max-w-lg w-full">
|
||||
<div class="flex justify-center items-center flex-col mb-4">
|
||||
<h1 class="staatliches text-6xl">Create a note</h1>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<strong>What do you want to say?</strong>
|
||||
<Textarea rows="8" name="content" bind:value={values.content} />
|
||||
<div class="border-l-2 border-solid border-medium pl-4">
|
||||
<Compose bind:this={input} {onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" class="text-center">Send</Button>
|
||||
</div>
|
||||
|
@ -26,14 +26,6 @@
|
||||
{:else}
|
||||
<Global />
|
||||
{/if}
|
||||
<div class="fixed bottom-0 right-0 p-8">
|
||||
<a
|
||||
href="/notes/new"
|
||||
class="rounded-full bg-accent color-white w-16 h-16 flex justify-center
|
||||
items-center border border-dark shadow-2xl cursor-pointer">
|
||||
<span class="fa-sold fa-plus fa-2xl" />
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full justify-center items-center py-16">
|
||||
<div class="text-center max-w-sm">
|
||||
|
@ -74,3 +74,13 @@ export const killEvent = e => {
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
export const fromParentOffset = (element, offset) => {
|
||||
for (const child of element.childNodes) {
|
||||
if (offset <= child.textContent.length) {
|
||||
return [child, offset]
|
||||
}
|
||||
|
||||
offset -= child.textContent.length
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ export const createScroller = loadMore => {
|
||||
// Give it a generous timeout from last time something did load
|
||||
timeout = setTimeout(() => {
|
||||
didLoad = false
|
||||
}, 5000)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
didLoad = shouldLoad
|
||||
|
@ -29,7 +29,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
const cursor = new Cursor(timedelta(10, 'minutes'))
|
||||
const cursor = new Cursor(timedelta(20, 'minutes'))
|
||||
|
||||
const loadNotes = async () => {
|
||||
const [since, until] = cursor.step()
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
export let pubkey
|
||||
|
||||
const cursor = new Cursor(timedelta(1, 'days'))
|
||||
const cursor = new Cursor(timedelta(3, 'days'))
|
||||
|
||||
const loadNotes = async () => {
|
||||
const [since, until] = cursor.step()
|
||||
|
Loading…
Reference in New Issue
Block a user