Use lib version of date functions

This commit is contained in:
Jon Staab
2025-05-05 10:11:02 -07:00
parent 5873e8aa60
commit c1b52b66ff
21 changed files with 221 additions and 150 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
A discord-like nostr client based on the idea of "relays as groups".
If you would like to be interoperable with Flotilla, please check out this draft NIP: https://github.com/coracle-social/nips/blob/relay-chat/xx.md
If you would like to be interoperable with Flotilla, please check out this guide: https://habla.news/u/hodlbod@coracle.social/1741286140797
# Deploy
+1 -27
View File
@@ -2,7 +2,7 @@ import * as nip19 from "nostr-tools/nip19"
import {get} from "svelte/store"
import {randomId, ifLet, poll, uniq, equals} from "@welshman/lib"
import type {Feed} from "@welshman/feeds"
import type {TrustedEvent, EventContent, EventTemplate} from "@welshman/util"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {
DELETE,
REPORT,
@@ -34,14 +34,12 @@ import {
RelayMode,
} from "@welshman/util"
import {Pool, PublishStatus, AuthStatus, SocketStatus} from "@welshman/net"
import {Nip59, stamp} from "@welshman/signer"
import {Router} from "@welshman/router"
import {
pubkey,
signer,
repository,
publishThunk,
MergedThunk,
profilesByPubkey,
relaySelectionsByPubkey,
tagEvent,
@@ -336,30 +334,6 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
// Actions
export const sendWrapped = async ({
template,
pubkeys,
delay,
}: {
template: EventTemplate
pubkeys: string[]
delay?: number
}) => {
const nip59 = Nip59.fromSigner(signer.get()!)
return new MergedThunk(
await Promise.all(
uniq(pubkeys).map(async recipient =>
publishThunk({
event: await nip59.wrap(recipient, stamp(template)),
relays: Router.get().PubkeyInbox(recipient).getUrls(),
delay,
}),
),
),
)
}
export const makeDelete = ({event}: {event: TrustedEvent}) => {
const tags = [["k", String(event.kind)], ...tagEvent(event)]
const groupTag = getTag("h", event.tags)
+1 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import {fromPairs} from "@welshman/lib"
import {fromPairs, LOCALE, secondsToDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {LOCALE, secondsToDate} from "@welshman/app"
type Props = {
event: TrustedEvent
@@ -1,8 +1,12 @@
<script lang="ts">
import {fromPairs} from "@welshman/lib"
import {
fromPairs,
formatTimestamp,
formatTimestampAsDate,
formatTimestampAsTime,
} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import Icon from "@lib/components/Icon.svelte"
import {formatTimestamp, formatTimestampAsDate, formatTimestampAsTime} from "@welshman/app"
type Props = {
event: TrustedEvent
+2 -9
View File
@@ -1,14 +1,7 @@
<script lang="ts">
import {hash, now} from "@welshman/lib"
import {hash, now, formatTimestampAsTime, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {
thunks,
pubkey,
formatTimestampAsDate,
formatTimestampAsTime,
deriveProfile,
deriveProfileDisplay,
} from "@welshman/app"
import {thunks, pubkey, deriveProfile, deriveProfileDisplay} from "@welshman/app"
import {isMobile} from "@lib/html"
import TapTarget from "@lib/components/TapTarget.svelte"
import Avatar from "@lib/components/Avatar.svelte"
+55 -52
View File
@@ -1,14 +1,14 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {onMount} from "svelte"
import {int, nthNe, MINUTE, sortBy, remove} from "@welshman/lib"
import {int, nthNe, MINUTE, sortBy, remove, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {createEvent, DIRECT_MESSAGE, INBOX_RELAYS} from "@welshman/util"
import {
pubkey,
tagPubkey,
sendWrapped,
loadUsingOutbox,
formatTimestampAsDate,
inboxRelaySelectionsByPubkey,
} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
@@ -24,8 +24,8 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ProfileList from "@app/components/ProfileList.svelte"
import ChatMessage from "@app/components/ChatMessage.svelte"
import ChatCompose from "@app/components/ChannelCompose.svelte"
import ChatComposeParent from "@app/components/ChannelComposeParent.svelte"
import ChatCompose from "@app/components/ChatCompose.svelte"
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
import {
INDEXER_RELAYS,
userSettingValues,
@@ -34,7 +34,7 @@
PLATFORM_NAME,
} from "@app/state"
import {pushModal} from "@app/modal"
import {sendWrapped, prependParent} from "@app/commands"
import {prependParent} from "@app/commands"
type Props = {
id: string
@@ -137,54 +137,57 @@
}, 5000)
</script>
{#if others.length > 0}
<PageBar>
{#snippet title()}
<div class="flex flex-col gap-1 sm:flex-row sm:gap-2">
{#if others.length === 1}
{@const pubkey = others[0]}
{@const onClick = () => pushModal(ProfileDetail, {pubkey})}
<Button onclick={onClick} class="row-2">
<ProfileCircle {pubkey} size={5} />
<ProfileName {pubkey} />
</Button>
{:else}
<div class="flex items-center gap-2">
<ProfileCircles pubkeys={others} size={5} />
<p class="overflow-hidden text-ellipsis whitespace-nowrap">
<ProfileName pubkey={others[0]} />
and
{#if others.length === 2}
<ProfileName pubkey={others[1]} />
{:else}
{others.length - 1}
{others.length > 2 ? "others" : "other"}
{/if}
</p>
</div>
{#if others.length > 2}
<Button onclick={showMembers} class="btn btn-link hidden sm:block"
>Show all members</Button>
{/if}
<PageBar>
{#snippet title()}
<div class="flex flex-col gap-1 sm:flex-row sm:gap-2">
{#if others.length === 0}
<div class="row-2">
<ProfileCircle pubkey={$pubkey!} size={5} />
<ProfileName pubkey={$pubkey!} />
</div>
{:else if others.length === 1}
{@const pubkey = others[0]}
{@const onClick = () => pushModal(ProfileDetail, {pubkey})}
<Button onclick={onClick} class="row-2">
<ProfileCircle {pubkey} size={5} />
<ProfileName {pubkey} />
</Button>
{:else}
<div class="flex items-center gap-2">
<ProfileCircles pubkeys={others} size={5} />
<p class="overflow-hidden text-ellipsis whitespace-nowrap">
<ProfileName pubkey={others[0]} />
and
{#if others.length === 2}
<ProfileName pubkey={others[1]} />
{:else}
{others.length - 1}
{others.length > 2 ? "others" : "other"}
{/if}
</p>
</div>
{#if others.length > 2}
<Button onclick={showMembers} class="btn btn-link hidden sm:block"
>Show all members</Button>
{/if}
</div>
{/snippet}
{#snippet action()}
<div>
{#if remove($pubkey, missingInboxes).length > 0}
{@const count = remove($pubkey, missingInboxes).length}
{@const label = count > 1 ? "inboxes are" : "inbox is"}
<div
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
data-tip="{count} {label} not configured.">
<Icon icon="danger" />
{count}
</div>
{/if}
</div>
{/snippet}
</PageBar>
{/if}
{/if}
</div>
{/snippet}
{#snippet action()}
<div>
{#if remove($pubkey, missingInboxes).length > 0}
{@const count = remove($pubkey, missingInboxes).length}
{@const label = count > 1 ? "inboxes are" : "inbox is"}
<div
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
data-tip="{count} {label} not configured.">
<Icon icon="danger" />
{count}
</div>
{/if}
</div>
{/snippet}
</PageBar>
<PageContent class="flex flex-col-reverse pt-4">
<div bind:this={dynamicPadding}></div>
+58
View File
@@ -0,0 +1,58 @@
<script lang="ts">
import {writable} from "svelte/store"
import type {EventContent} from "@welshman/util"
import {isMobile, preventDefault} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {makeEditor} from "@app/editor"
type Props = {
url?: string
onSubmit: (event: EventContent) => void
}
const {onSubmit, url}: Props = $props()
const autofocus = !isMobile
const uploading = writable(false)
export const focus = () => editor.then(ed => ed.chain().focus().run())
const submit = async () => {
if ($uploading) return
const ed = await editor
const content = ed.getText({blockSeparator: "\n"}).trim()
const tags = ed.storage.nostr.getEditorTags()
if (!content) return
onSubmit({content, tags})
ed.chain().clearContent().run()
}
const editor = makeEditor({
url,
autofocus,
submit,
uploading,
aggressive: true,
disableFileUpload: true,
})
</script>
<form class="relative z-feature flex gap-2 p-2" onsubmit={preventDefault(submit)}>
<div class="chat-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
<Button
data-tip="{window.navigator.platform.includes('Mac') ? 'cmd' : 'ctrl'}+enter to send"
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
disabled={$uploading}
onclick={submit}>
<Icon icon="plain" />
</Button>
</form>
@@ -0,0 +1,35 @@
<script lang="ts">
import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app"
import {slide} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
const {
verb,
event,
clear,
}: {
verb: string
event: TrustedEvent
clear: () => void
} = $props()
</script>
<div
class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8 text-xs"
transition:slide>
<p class="text-primary">{verb} @{displayProfileByPubkey(event.pubkey)}</p>
{#key event.id}
<NoteContent
{event}
hideMediaAtDepth={0}
minLength={100}
maxLength={300}
expandMode="disabled" />
{/key}
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
<Icon icon="close-circle" />
</Button>
</div>
+3 -9
View File
@@ -1,14 +1,8 @@
<script lang="ts">
import {type Instance} from "tippy.js"
import {hash} from "@welshman/lib"
import {hash, formatTimestampAsTime} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {
thunks,
formatTimestampAsTime,
pubkey,
deriveProfile,
deriveProfileDisplay,
} from "@welshman/app"
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
import {isMobile} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -22,7 +16,7 @@
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
import {colors} from "@app/state"
import {makeDelete, makeReaction, sendWrapped} from "@app/commands"
import {makeDelete, makeReaction} from "@app/commands"
import {pushModal} from "@app/modal"
interface Props {
@@ -1,9 +1,10 @@
<script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte"
import {makeReaction, sendWrapped} from "@app/commands"
import {makeReaction} from "@app/commands"
interface Props {
event: TrustedEvent
@@ -1,11 +1,12 @@
<script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import EmojiPicker from "@lib/components/EmojiPicker.svelte"
import EventInfo from "@app/components/EventInfo.svelte"
import {makeReaction, sendWrapped} from "@app/commands"
import {makeReaction} from "@app/commands"
import {pushModal} from "@app/modal"
import {clip} from "@app/toast"
+2 -2
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import {onMount} from "svelte"
import {max} from "@welshman/lib"
import {max, formatTimestampRelative} from "@welshman/lib"
import {COMMENT} from "@welshman/util"
import {load} from "@welshman/net"
import {deriveEvents} from "@welshman/store"
import type {TrustedEvent} from "@welshman/util"
import {formatTimestampRelative, repository} from "@welshman/app"
import {repository} from "@welshman/app"
import {notifications} from "@app/notifications"
import Icon from "@lib/components/Icon.svelte"
+2 -1
View File
@@ -2,10 +2,11 @@
import cx from "classnames"
import type {Snippet} from "svelte"
import * as nip19 from "nostr-tools/nip19"
import {formatTimestamp} from "@welshman/lib"
import {getListTags, getPubkeyTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Router} from "@welshman/router"
import {formatTimestamp, userMutes} from "@welshman/app"
import {userMutes} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
+2 -1
View File
@@ -1,10 +1,11 @@
<script lang="ts">
import {onMount} from "svelte"
import {formatTimestampRelative} from "@welshman/lib"
import type {Filter} from "@welshman/util"
import {deriveEvents} from "@welshman/store"
import {load} from "@welshman/net"
import {Router} from "@welshman/router"
import {repository, loadRelaySelections, formatTimestampRelative} from "@welshman/app"
import {repository, loadRelaySelections} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Profile from "@app/components/Profile.svelte"
+1 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import {nthEq} from "@welshman/lib"
import {nthEq, formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {formatTimestamp} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
import Content from "@app/components/Content.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
+13 -9
View File
@@ -71,6 +71,7 @@ export const makeEditor = async ({
submit,
uploading,
wordCount,
disableFileUpload,
}: {
aggressive?: boolean
autofocus?: boolean
@@ -81,6 +82,7 @@ export const makeEditor = async ({
submit: () => void
uploading?: Writable<boolean>
wordCount?: Writable<number>
disableFileUpload?: boolean
}) => {
return new Editor({
content,
@@ -103,16 +105,18 @@ export const makeEditor = async ({
aggressive,
},
},
fileUpload: {
config: {
onDrop() {
uploading?.set(true)
fileUpload: disableFileUpload
? false
: {
config: {
onDrop() {
uploading?.set(true)
},
onComplete() {
uploading?.set(false)
},
},
},
onComplete() {
uploading?.set(false)
},
},
},
nprofile: {
extend: {
addNodeView: () => makeMentionNodeView(url),
+1 -1
View File
@@ -304,7 +304,7 @@ export const defaultSettings = {
show_media: true,
hide_sensitive: true,
report_usage: true,
report_errors: false,
report_errors: true,
send_delay: 3000,
}
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import {DateInput} from "date-picker-svelte"
import {secondsToDate, dateToSeconds} from "@welshman/app"
import {secondsToDate, dateToSeconds} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
+30 -25
View File
@@ -15,6 +15,7 @@
THREAD,
MESSAGE,
INBOX_RELAYS,
DIRECT_MESSAGE,
MUTES,
FOLLOWS,
PROFILE,
@@ -131,31 +132,6 @@
}
})
await initStorage("flotilla", 8, {
...defaultStorageAdapters,
events: new EventsStorageAdapter({
name: "events",
limit: 10_000,
repository,
rankEvent: (e: TrustedEvent) => {
if ([PROFILE, FOLLOWS, MUTES, RELAYS, BLOSSOM_SERVERS, INBOX_RELAYS].includes(e.kind))
return 1
if ([EVENT_TIME, THREAD, MESSAGE, WRAP].includes(e.kind)) return 0.9
return 0
},
}),
})
sleep(300).then(() => ready.resolve())
defaultSocketPolicies.push(
makeSocketPolicyAuth({
sign: (event: StampedEvent) => signer.get()?.sign(event),
shouldAuth: (socket: Socket) => true,
}),
)
// Unwrap gift wraps as they come in, but throttled
const unwrapper = new TaskQueue<TrustedEvent>({batchSize: 10, processItem: ensureUnwrapped})
@@ -171,6 +147,35 @@
}
})
await initStorage("flotilla", 8, {
...defaultStorageAdapters,
events: new EventsStorageAdapter({
name: "events",
limit: 10_000,
repository,
rankEvent: (e: TrustedEvent) => {
if ([PROFILE, FOLLOWS, MUTES, RELAYS, BLOSSOM_SERVERS, INBOX_RELAYS].includes(e.kind)) {
return 1
}
if ([EVENT_TIME, THREAD, MESSAGE, DIRECT_MESSAGE].includes(e.kind)) {
return 0.9
}
return 0
},
}),
})
sleep(300).then(() => ready.resolve())
defaultSocketPolicies.push(
makeSocketPolicyAuth({
sign: (event: StampedEvent) => signer.get()?.sign(event),
shouldAuth: (socket: Socket) => true,
}),
)
// Load relay info
for (const url of INDEXER_RELAYS) {
loadRelay(url)
@@ -3,10 +3,10 @@
import {onMount, onDestroy} from "svelte"
import {page} from "$app/stores"
import type {Readable} from "svelte/store"
import {now} from "@welshman/lib"
import {now, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
import {formatTimestampAsDate, pubkey, publishThunk, deriveRelay} from "@welshman/app"
import {pubkey, publishThunk, deriveRelay} from "@welshman/app"
import {slide, fade, fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -3,10 +3,9 @@
import type {Readable} from "svelte/store"
import {readable} from "svelte/store"
import {page} from "$app/stores"
import {now, last} from "@welshman/lib"
import {now, last, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {REACTION, DELETE, EVENT_TIME, getTagValue} from "@welshman/util"
import {formatTimestampAsDate} from "@welshman/app"
import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"