aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-03-26 08:24:16 -0700
committerAdam Soltys <[email protected]>2024-03-26 08:24:16 -0700
commit5caa340b1d4fc4dc4cc0ddfb54527c69ebdfc25d (patch)
tree6cd76b847c51269429200844a7e5a9fe68981fe2 /src
parentfb20dfb656903dfa32774b130692fae9c6df947a (diff)
downloadcoinos-ui-5caa340b1d4fc4dc4cc0ddfb54527c69ebdfc25d.tar.gz
coinos-ui-5caa340b1d4fc4dc4cc0ddfb54527c69ebdfc25d.zip
deps
Diffstat (limited to 'src')
-rw-r--r--src/routes/(app)/[username]/messages/+page.svelte227
1 files changed, 68 insertions, 159 deletions
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte
index b36eab2a..4738ea2c 100644
--- a/src/routes/(app)/[username]/messages/+page.svelte
+++ b/src/routes/(app)/[username]/messages/+page.svelte
@@ -1,183 +1,92 @@
<script>
- import { cubicInOut } from "svelte/easing";
- import { browser } from "$app/environment";
import { t } from "$lib/translations";
- import { format } from "date-fns";
- import { scale, fade, fly } from "svelte/transition";
- import { enhance } from "$app/forms";
import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { back, fail, focus } from "$lib/utils";
- import { sign, send, encrypt, decrypt } from "$lib/nostr";
import { event as e, password } from "$lib/store";
- import { tick, onMount } from "svelte";
- import { getEventHash } from "@asoltys/nostr-tools";
+ import { browser } from "$app/environment";
+ import { decrypt } from "$lib/nostr";
export let data;
- function fadeScale(
- node,
- { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
- ) {
- const o = +getComputedStyle(node).opacity;
- const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
- const s = m ? m[1] : 1;
- const is = 1 - baseScale;
-
- return {
- delay,
- duration,
- css: (t) => {
- const eased = easing(t);
- return `opacity: ${eased * o}; transform: scale(${
- eased * s * is + baseScale
- })`;
- },
- };
- }
+ let { messages, notes, invoices, sent, received, subject, user, rates } =
+ data;
+ $: refresh(data);
+ let refresh = (d) =>
+ ({ messages, notes, invoices, sent, received, subject, user, rates } = d);
- let { messages, subject, user } = data;
- let input, pane;
+ let keys = new Set();
+ let latest = [];
+ let ready;
- $: initialize($password);
- let initialize = async (p) => {
- if (!p) return;
- await Promise.all(
- messages.map(
- async (event) => (event.message = await decrypt({ event, user }))
- )
- );
+ e.subscribe(async (event) => {
+ if (!(browser && event && ready)) return;
+ if (
+ event.recipient?.id === user.id &&
+ !~latest.findIndex((m) => m.id === event.id)
+ ) {
+ event.content = await decrypt({ event, user });
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- };
+ let i = latest.findIndex((m) => m.pubkey === event.pubkey);
+ let popped;
+ if (~i) popped = latest.splice(i, 1);
+ else popped = latest.pop();
- e.subscribe(async (event) => {
- let found = ~messages.findIndex((m) => m.id === event?.id);
- if (event?.recipient.id === user.id && !found) {
- event.message = await decrypt({ event, user });
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ latest.unshift(event);
+ latest = latest;
}
});
- let sent, submitting, message;
- let submit = async () => {
- submitting = true;
-
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 4,
- tags: [["p", subject.pubkey]],
- };
-
- event.message = message;
-
- message = "";
-
- event.author = user;
- event.recipient = subject;
+ $: initialize($password);
+ let initialize = async (p) => {
+ let i = 0;
+ ready = false;
+ if (!(browser && user)) return;
+ while (i < messages.length) {
+ let event = messages[i];
+ i++;
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ if (!(event.author && event.recipient)) continue;
- try {
- event.content = await encrypt({
- message: event.message,
- recipient: subject.pubkey,
- user,
- });
- event.id = getEventHash(event);
- await sign({ event, user });
- await send(event);
+ let k =
+ event.author.id === user.id
+ ? event.recipient?.pubkey
+ : event.author.pubkey;
- sent = true;
- } catch (e) {
- console.log(e);
- fail("Failed to send message");
+ if (!keys.has(k)) {
+ keys.add(k);
+ event.content = await decrypt({ event, user });
+ if (event.content) latest.push(event);
+ }
}
- message = "";
- submitting = false;
- };
-
- let keydown = (e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- submit();
- }
+ latest = latest;
+ ready = true;
};
</script>
-<div class="container max-w-xl mx-auto px-4 space-y-5">
- <div
- class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
- bind:this={pane}
- >
- {#each messages as { id, author, message, created_at, pubkey }}
- {@const ours = pubkey === user.pubkey}
- {@const theirs = !ours}
-
- {#if message}
- <div
- class="flex gap-2"
- class:flex-row-reverse={theirs}
- class:justify-end={theirs}
- in:fadeScale={{
- easing: cubicInOut,
- baseScale: 0.5,
- }}
- >
- <div
- class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
- class:ours
- class:theirs
- :key={id}
- >
- {message}
+<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
+ {$t("user.messages")}
+ </h1>
+ {#if latest.length && user?.id === subject.id}
+ <div class="relative">
+ {#each latest as { content, pubkey, author, recipient }}
+ {@const user = author.id === user.id ? recipient : author}
+ <a href={`/${subject.username}/messages/${user.username}`}>
+ <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
+ <div class="my-auto">
+ <Avatar {user} size={"20"} disabled={true} />
+ </div>
+ <div class="my-auto truncate">
+ <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
+ <div class="my-auto ml-1 text-secondary text-lg truncate">
+ {content}
+ </div>
+ </div>
</div>
- <div class="mt-auto">
- <Avatar user={author} size={"12"} />
- </div>
- </div>
- <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
- {format(new Date(created_at * 1000), "MMM d")},
- {format(new Date(created_at * 1000), "h:mm aa")}
- </div>
- {/if}
- {/each}
- </div>
-
- <form
- method="POST"
- class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
- on:submit|preventDefault={submit}
- >
- <input type="hidden" name="requester_id" value={user.id} />
- <input type="hidden" name="recipient" value={subject.username} />
-
- <div
- use:focus
- contenteditable
- class="grow break-all py-4 outline-none mt-0 pl-4"
- bind:innerHTML={message}
- on:keydown={keydown}
- />
- <button type="submit" class="my-auto shrink-0">
- <Icon icon="send" style="w-8" />
- </button>
- </form>
+ </a>
+ {/each}
+ </div>
+ {:else}
+ <div class="text-center">{$t("user.noMessages")}</div>
+ {/if}
</div>
-
-<style>
- .ours {
- @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
- }
-
- .theirs {
- @apply bg-gray-100 text-black rounded-bl-none;
- }
-</style>