Skip to content

Commit

Permalink
Improve UI, add cache and fix some ts stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
sant0s12 committed Mar 3, 2024
1 parent 557a487 commit 852412d
Show file tree
Hide file tree
Showing 12 changed files with 1,263 additions and 252 deletions.
1,033 changes: 951 additions & 82 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"type": "module",
"dependencies": {
"@nostr-dev-kit/ndk": "^2.5.0",
"@nostr-dev-kit/ndk-cache-dexie": "^2.2.7",
"@nostr-dev-kit/ndk-svelte": "^2.2.7",
"date-fns": "^3.3.1",
"dompurify": "^3.0.9",
Expand Down
Binary file added src/lib/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/lib/components/PostCard.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script lang="ts">
import { Post } from '$lib/nostr';
import { CommunityPost } from '$lib/nostr';
import { Card, Heading } from 'flowbite-svelte';
import PostInteraction from '$lib/components/PostInteraction.svelte';
import { base } from '$app/paths';
import PostHeader from './PostHeader.svelte';
export let post: Post;
export let post: CommunityPost;
export let showCommunity: boolean = false;
let { content, title, id } = post;
Expand Down
141 changes: 67 additions & 74 deletions src/lib/components/PostHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,67 +1,58 @@
<script lang="ts">
import { base } from '$app/paths';
import Avatar from '$lib/components/Avatar.svelte';
import { npubEncodeShort, type Community, type Post } from '$lib/nostr';
import { npubShort, CommunityPost } from '$lib/nostr';
import DOMPurify from 'dompurify';
import { ArrowsRepeatOutline, BadgeCheckSolid } from 'flowbite-svelte-icons';
import { onMount } from 'svelte';
import { formatDistance } from 'date-fns';
export let post: Post;
export let post: CommunityPost;
export let showCommunity: boolean = false;
let communities: Community[] = [];
let communityLinks: string[] = [];
let joinedLinks: string = '';
$: {
for (const community of communities) {
community.name = DOMPurify.sanitize(community.name, { ALLOWED_TAGS: [] });
community.id = DOMPurify.sanitize(community.id, { ALLOWED_TAGS: [] });
if (showCommunity) {
post.getCommunities().then((communities) => {
for (const community of communities) {
community.name = DOMPurify.sanitize(community.name, { ALLOWED_TAGS: [] });
community.id = DOMPurify.sanitize(community.id, { ALLOWED_TAGS: [] });
communityLinks.push(
`<a href="${base}/c/${community.id}" class="hover:underline"> <p>${community.name}</p> </a>`
);
communityLinks.push(
`<a href="${base}/c/${community.id}" class="hover:underline"> <p>${community.name}</p> </a>`
);
communityLinks = communityLinks;
}
communityLinks = communityLinks;
}
joinedLinks = communityLinks.join('<span class="text-gray-400"> • </span>');
joinedLinks = communityLinks.join('<span class="text-gray-400"> • </span>');
});
}
$: if (post.author.profile === undefined) post.author.fetchProfile().then(() => (post = post));
let postTime = post.created_at ? formatDistance(new Date(post.created_at * 1000), new Date(), {
addSuffix: true
}) : 'some time ago';
// Await the communities
onMount(async () => {
if (showCommunity && post.communities && post.communities.length > 0) {
post.communities.forEach(async (community) => {
let awatedCommunity = await community;
if (awatedCommunity) {
communities.push(awatedCommunity);
communities = communities;
}
});
}
});
let postTime = post.created_at
? formatDistance(new Date(post.created_at * 1000), new Date(), {
addSuffix: true
})
: 'some time ago';
</script>

<div class="flex flex-row w-auto space-x-2 items-center">
{#if showCommunity && post.communities && post.communities.length > 0}
<div class="flex -space-x-4 rtl:space-x-reverse">
{#each communities as community}
<a href="{base}/c/{community.id}">
<Avatar
src={community.image}
fallback={community.name}
size={'sm'}
class={communities.length > 1 ? 'border-[2px] dark:border-gray-800 border-white' : ''}
/>
</a>
{/each}
{#await post.getCommunities() then communities}
{#each communities as community}
<a href="{base}/c/{community.id}">
<Avatar
src={community.image}
fallback={community.name}
size={'sm'}
class={communities.length > 1 ? 'border-[2px] dark:border-gray-800 border-white' : ''}
/>
</a>
{/each}
{/await}
</div>
{:else}
<a href="{base}/p/{post.author.pubkey}">
Expand All @@ -82,48 +73,50 @@
{#if post.author.profile?.name}
<p>{post.author.profile.name}</p>
{:else}
<p>{npubEncodeShort(post.author.pubkey)}</p>
<p>{npubShort(post.author.npub)}</p>
{/if}
{#if post.author.profile?.nip05}
{#await post.author.validateNip05(post.author.profile.nip05) then validated}
{#if validated}
<BadgeCheckSolid size="xs" />
<div>
{post.author.profile.nip05}
</div>
{/if}
{/await}
{/if}
<!-- {#if post.author.verified}
<BadgeCheckSolid size="xs" />
<div>
{post.author.nip05}
</div>
{/if} -->
</a>
<p>•</p>
<p>{postTime}</p>
</div>
</div>
</div>
{#if post.repostedBy}
{#each post.repostedBy as repostedBy}
{#await repostedBy then repostedBy}
{#await repostedBy.author.fetchProfile() then repostAuthor}
{#if repostAuthor}
<div class="flex flex-row overflow-hidden space-x-2 items-center">
<ArrowsRepeatOutline size="xs" />
<div class="flex flex-col text-xs justify-center items-start">
<a
href="{base}/p/{repostAuthor.pubkey}"
class="inline-flex space-x-1 items-center hover:underline"
>
{#if repostAuthor.name}
<p>{repostAuthor.name}</p>
{:else if repostAuthor.pubkey}
<p>{npubEncodeShort(repostAuthor.pubkey)}</p>
{/if}
{#if repostAuthor.verified}
<BadgeCheckSolid size="xs" />
<div>
{repostAuthor.nip05}
</div>
{/if}
</a>
</div>
{#if post.repostedByEvents}
{#each post.repostedByEvents as repostedBy}
{#await repostedBy.author.fetchProfile() then repostAuthor}
{#if repostAuthor}
<div class="flex flex-row overflow-hidden space-x-2 items-center">
<ArrowsRepeatOutline size="xs" />
<div class="flex flex-col text-xs justify-center items-start">
<a
href="{base}/p/{repostAuthor.pubkey}"
class="inline-flex space-x-1 items-center hover:underline"
>
{#if repostAuthor.name}
<p>{repostAuthor.name}</p>
{:else}
<p>{npubShort(repostedBy.author.npub)}</p>
{/if}
{#if repostAuthor.verified}
<BadgeCheckSolid size="xs" />
<div>
{repostAuthor.nip05}
</div>
{/if}
</a>
</div>
{/if}
{/await}
</div>
{/if}
{/await}
{/each}
{/if}
112 changes: 76 additions & 36 deletions src/lib/components/PostInteraction.svelte
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
<script lang="ts">
import { base } from '$app/paths';
import { getPostReactions, type Post } from '$lib/nostr';
import { getPostReactions, CommunityPost } from '$lib/nostr';
import ndk from '$lib/stores/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { Button, ButtonGroup } from 'flowbite-svelte';
import { Button, ButtonGroup, Spinner } from 'flowbite-svelte';
import { ChevronDownOutline, ChevronUpOutline, MessageDotsSolid } from 'flowbite-svelte-icons';
import { kinds } from 'nostr-tools';
import { onMount } from 'svelte';
import { toast } from 'svelte-french-toast';
export let post: Post;
export let post: CommunityPost;
let comments = 0;
let reactions: Map<string, number> = new Map();
$: upvotes = reactions.get('+') || 0;
$: downvotes = reactions.get('-') || 0;
let userVote = 0;
let clickedVote = 0;
onMount(() => {
getPostReactions(post).then((r) => (reactions = r));
getUserVote().then((v) => (userVote = v));
});
$: if ($ndk) getUserVote().then((v) => (userVote = v));
async function getUserVote() {
if ($ndk.activeUser) {
let event = await $ndk.fetchEvent({
authors: [$ndk.activeUser.pubkey],
kinds: [kinds.Reaction],
'#e': [post.id],
'#p': [post.author.pubkey]
});
let events = await $ndk
.fetchEvents({
authors: [$ndk.activeUser.pubkey],
kinds: [kinds.Reaction],
'#e': [post.id],
'#p': [post.author.pubkey]
})
.then((events) =>
Array.from(events)
.filter((event) => event.content === '+' || event.content === '-')
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
);
let lastVote = events.at(0)?.content;
if (event?.content === '+') {
if (lastVote === '+') {
return 1;
} else if (event?.content === '-') {
} else if (lastVote === '-') {
return -1;
} else {
return 0;
Expand All @@ -44,41 +55,70 @@
}
async function vote(vote: number) {
if (userVote == vote || !$ndk.activeUser) {
if (!$ndk.activeUser) {
return;
}
let reactionEvent = new NDKEvent($ndk, {
kind: kinds.Reaction,
pubkey: $ndk.activeUser.pubkey,
created_at: Math.round(new Date().getTime() / 1000),
content: vote == 1 ? '+' : '-',
tags: [
['e', post.id],
['p', post.author.pubkey]
]
});
clickedVote = vote;
await reactionEvent.publish();
if (userVote == vote) {
vote = 0;
}
userVote = await getUserVote();
if (vote == 1) {
reactions = reactions.set('+', (reactions.get('+') || 0) + 1);
} else {
reactions = reactions.set('-', (reactions.get('-') || 0) + 1);
// Remove existing vote
let events = await $ndk
.fetchEvents({
authors: [$ndk.activeUser.pubkey],
kinds: [kinds.Reaction],
'#e': [post.id],
'#p': [post.author.pubkey]
})
.then((events) =>
Array.from(events).filter((event) => event.content === '+' || event.content === '-')
);
for (const event of events) {
await event.delete('Removing vote', true);
}
if (vote != 0) {
await post.react(vote == 1 ? '+' : '-', true);
}
reactions = await getPostReactions(post);
userVote = await getUserVote();
clickedVote = 0;
}
</script>

<div class="flex flex-row w-full pt-2 space-x-2 items-stretch h-10">
<ButtonGroup>
<Button size="xs" on:click={() => vote(1)} color={userVote == 1 ? 'primary' : undefined}>
<ChevronUpOutline />
{upvotes}
<Button
disabled={clickedVote != 0}
size="xs"
on:click={() => vote(1)}
color={userVote == 1 ? 'primary' : undefined}
>
{#if clickedVote == 1}
<Spinner size="4" />
{:else}
<ChevronUpOutline />
{upvotes}
{/if}
</Button>
<Button size="xs" on:click={() => vote(-1)} color={userVote == -1 ? 'primary' : undefined}>
<ChevronDownOutline />
{downvotes}
<Button
disabled={clickedVote != 0}
size="xs"
on:click={() => vote(-1)}
color={userVote == -1 ? 'primary' : undefined}
>
{#if clickedVote == -1}
<Spinner size="4" />
{:else}
<ChevronDownOutline />
{downvotes}
{/if}
</Button>
</ButtonGroup>
<Button href="{base}/post/{post.id}" color="alternative" size="xs">
Expand Down
Loading

0 comments on commit 852412d

Please sign in to comment.