Browse Source

Pagination

master
Nuša Pukšič 7 months ago committed by buttercat1791
parent
commit
cdf4fca1ac
  1. 1
      src/lib/a/index.ts
  2. 63
      src/lib/a/primitives/APagination.svelte
  3. 124
      src/lib/components/Notifications.svelte
  4. 10
      src/routes/profile/+page.svelte

1
src/lib/a/index.ts

@ -7,6 +7,7 @@ export { default as ANostrBadge } from './primitives/ANostrBadge.svelte';
export { default as ANostrBadgeRow } from './primitives/ANostrBadgeRow.svelte'; export { default as ANostrBadgeRow } from './primitives/ANostrBadgeRow.svelte';
export { default as AThemeToggleMini } from './primitives/AThemeToggleMini.svelte'; export { default as AThemeToggleMini } from './primitives/AThemeToggleMini.svelte';
export { default as AAlert } from './primitives/AAlert.svelte'; export { default as AAlert } from './primitives/AAlert.svelte';
export { default as APagination } from './primitives/APagination.svelte';
export { default as AReaderPage } from './reader/AReaderPage.svelte'; export { default as AReaderPage } from './reader/AReaderPage.svelte';
export { default as AReaderToolbar } from './reader/AReaderToolbar.svelte'; export { default as AReaderToolbar } from './reader/AReaderToolbar.svelte';

63
src/lib/a/primitives/APagination.svelte

@ -0,0 +1,63 @@
<script lang="ts">
type Props = {
currentPage: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
totalItems?: number;
itemsLabel?: string;
className?: string;
};
let {
currentPage = $bindable<number>(1),
totalPages = 1,
hasNextPage = false,
hasPreviousPage = false,
totalItems = 0,
itemsLabel = 'items',
className = ''
} = $props<{
currentPage: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
totalItems?: number;
itemsLabel?: string;
className?: string;
}>();
function next() {
if (hasNextPage) currentPage = currentPage + 1;
}
function previous() {
if (hasPreviousPage) currentPage = currentPage - 1;
}
</script>
{#if totalPages > 1}
<div class={`mt-4 flex flex-row items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg ${className}`}>
<div class="text-sm text-gray-600 dark:text-gray-400">
Page {currentPage} of {totalPages} ({totalItems} total {itemsLabel})
</div>
<div class="flex flex-row items-center gap-2">
<button
class="px-3 py-1 !mb-0 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
onclick={previous}
disabled={!hasPreviousPage}
>
Previous
</button>
<span class="!mb-0 text-sm text-gray-600 dark:text-gray-400">
{currentPage} / {totalPages}
</span>
<button
class="px-3 py-1 !mb-0 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
onclick={next}
disabled={!hasNextPage}
>
Next
</button>
</div>
</div>
{/if}

124
src/lib/components/Notifications.svelte

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import "../../styles/notifications.css"; import "../../styles/notifications.css";
import { Heading, P } from "flowbite-svelte"; import { Heading, P, Avatar, Button, Modal } from "flowbite-svelte";
import type { NDKEvent } from "$lib/utils/nostrUtils"; import type { NDKEvent } from "$lib/utils/nostrUtils";
import { userStore } from "$lib/stores/userStore"; import { userStore } from "$lib/stores/userStore";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
@ -9,10 +9,9 @@
import { anonymousRelays } from "$lib/consts"; import { anonymousRelays } from "$lib/consts";
import { getKind24RelaySet } from "$lib/utils/kind24_utils"; import { getKind24RelaySet } from "$lib/utils/kind24_utils";
import { createSignedEvent } from "$lib/utils/nostrEventService"; import { createSignedEvent } from "$lib/utils/nostrEventService";
import { Modal, Button } from "flowbite-svelte";
import { searchProfiles } from "$lib/utils/search_utility"; import { searchProfiles } from "$lib/utils/search_utility";
import type { NostrProfile } from "$lib/utils/search_types"; import type { NostrProfile } from "$lib/utils/search_types";
import { PlusOutline, ReplyOutline, UserOutline } from "flowbite-svelte-icons"; import { ReplyOutline, UserOutline } from "flowbite-svelte-icons";
import { import {
getNotificationType, getNotificationType,
fetchAuthorProfiles, fetchAuthorProfiles,
@ -25,6 +24,7 @@
import { repostKinds } from "$lib/consts"; import { repostKinds } from "$lib/consts";
import { getNdkContext } from "$lib/ndk"; import { getNdkContext } from "$lib/ndk";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte"; import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
import { AAlert, APagination } from "$lib/a";
const ndk = getNdkContext(); const ndk = getNdkContext();
@ -845,7 +845,7 @@
</script> </script>
{#if isOwnProfile && $userStore.signedIn} {#if isOwnProfile && $userStore.signedIn}
<div class="mb-6 w-full overflow-x-hidden"> <div class="mb-6 w-full">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<Heading tag="h3" class="h-leather">Notifications</Heading> <Heading tag="h3" class="h-leather">Notifications</Heading>
@ -883,28 +883,21 @@
</span> </span>
</div> </div>
{:else if error} {:else if error}
<div class="p-4 bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300 rounded-lg"> <AAlert color="red">
<P>Error loading {notificationMode === "public-messages" ? "public messages" : "notifications"}: {error}</P> <P>Error loading {notificationMode === "public-messages" ? "public messages" : "notifications"}: {error}</P>
</div> </AAlert>
{:else if notificationMode === "public-messages"} {:else if notificationMode === "public-messages"}
{#if publicMessages.length === 0} {#if publicMessages.length === 0}
<div class="p-4 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-lg"> <AAlert color="blue"><P>No public messages found.</P></AAlert>
<P>No public messages found.</P>
</div>
{:else} {:else}
<div class="max-h-[72rem] overflow-y-auto overflow-x-hidden"> <div>
{#if filteredByUser} {#if filteredByUser}
<div class="filter-indicator mb-4 p-3 rounded-lg"> <div class="filter-indicator mb-4 p-3 rounded-lg">
<div class="flex items-center justify-between"> <div class="flex flex-row items-center justify-between gap-3">
<span class="text-sm text-blue-700 dark:text-blue-300"> <AAlert color="blue"><P>Filtered by user: @{authorProfiles.get(filteredByUser)?.displayName || authorProfiles.get(filteredByUser)?.name || "anon"}</P></AAlert>
Filtered by user: @{authorProfiles.get(filteredByUser)?.displayName || authorProfiles.get(filteredByUser)?.name || "anon"} <Button size="xs" color="blue" onclick={clearFilter}>
</span>
<button
class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200 underline font-medium"
onclick={clearFilter}
>
Clear Filter Clear Filter
</button> </Button>
</div> </div>
</div> </div>
{/if} {/if}
@ -913,21 +906,14 @@
{@const authorProfile = authorProfiles.get(message.pubkey)} {@const authorProfile = authorProfiles.get(message.pubkey)}
{@const isFromUser = message.pubkey === $userStore.pubkey} {@const isFromUser = message.pubkey === $userStore.pubkey}
<div class="message-container p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm" data-event-id="{message.id}"> <div class="message-container p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm" data-event-id="{message.id}">
<div class="flex items-start gap-3 {isFromUser ? 'flex-row-reverse' : ''}"> <div class="flex items-start gap-3 {isFromUser ? 'flex-row-reverse' : 'flex-row'}">
<!-- Author Profile Picture and Name --> <!-- Author Profile Picture and Name -->
<div class="flex-shrink-0 relative"> <div class="flex-shrink-0 relative">
<div class="flex flex-col items-center gap-2 {isFromUser ? 'items-end' : 'items-start'}"> <div class="flex flex-col items-center justify-center gap-2">
{#if authorProfile?.picture} {#if authorProfile?.picture}
<img <Avatar src={authorProfile.picture} onerror={hideImg} border></Avatar>
src={authorProfile.picture}
alt="Author avatar"
class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-gray-600"
onerror={hideImg}
/>
{:else} {:else}
<div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600"> <Avatar border />
<UserOutline class="w-5 h-5 text-gray-600 dark:text-gray-300" />
</div>
{/if} {/if}
<div class="w-24 text-center"> <div class="w-24 text-center">
<span class="text-xs font-medium text-gray-900 dark:text-gray-100 break-words"> <span class="text-xs font-medium text-gray-900 dark:text-gray-100 break-words">
@ -938,7 +924,7 @@
<!-- Filter button for non-user messages --> <!-- Filter button for non-user messages -->
{#if !isFromUser} {#if !isFromUser}
<div class="mt-2 flex justify-center gap-1"> <div class="mt-2 flex flex-row justify-center gap-1">
<!-- Reply button --> <!-- Reply button -->
<button <button
class="reply-button w-6 h-6 border border-gray-400 dark:border-gray-500 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full flex items-center justify-center text-xs transition-colors" class="reply-button w-6 h-6 border border-gray-400 dark:border-gray-500 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full flex items-center justify-center text-xs transition-colors"
@ -969,20 +955,20 @@
<!-- Message Content --> <!-- Message Content -->
<div class="message-content flex-1 min-w-0 {isFromUser ? 'text-right' : ''}"> <div class="message-content flex-1 min-w-0 {isFromUser ? 'text-right' : ''}">
<div class="flex items-center gap-2 mb-2 {isFromUser ? 'justify-end' : ''}"> <div class="flex flex-row items-center gap-2 mb-2 {isFromUser ? 'justify-end' : 'justify-start'}">
<span class="text-xs font-medium text-primary-600 dark:text-primary-400 bg-primary-100 dark:bg-primary-900 px-2 py-1 rounded"> <span class="text-xs !mb-0 font-medium text-primary-600 dark:text-primary-400 bg-primary-100 dark:bg-primary-900 px-2 py-1 rounded">
{isFromUser ? 'Your Message' : 'Public Message'} {isFromUser ? 'Your Message' : 'Public Message'}
</span> </span>
<span class="text-xs text-gray-500 dark:text-gray-400">
{message.created_at ? formatDate(message.created_at) : "Unknown date"}
</span>
<button <button
class="text-xs text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-mono" class="text-xs !mb-0 text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-mono"
onclick={() => navigateToEvent(getNeventUrl(message))} onclick={() => navigateToEvent(getNeventUrl(message))}
title="Click to view event" title="Click to view event"
> >
{getNeventUrl(message).slice(0, 16)}... {getNeventUrl(message).slice(0, 16)}...
</button> </button>
<span class="text-xs text-gray-500 dark:text-gray-400 ml-auto">
{message.created_at ? formatDate(message.created_at) : "Unknown date"}
</span>
</div> </div>
@ -1039,30 +1025,14 @@
<!-- Pagination Controls --> <!-- Pagination Controls -->
{#if totalPages > 1} {#if totalPages > 1}
<div class="mt-4 flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"> <APagination
<div class="text-sm text-gray-600 dark:text-gray-400"> bind:currentPage
Page {currentPage} of {totalPages} ({allPublicMessages.length} total messages) {totalPages}
</div> {hasNextPage}
<div class="flex items-center gap-2"> {hasPreviousPage}
<button totalItems={allPublicMessages.length}
class="px-3 py-1 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed" itemsLabel="messages"
onclick={previousPage} />
disabled={!hasPreviousPage}
>
Previous
</button>
<span class="text-sm text-gray-600 dark:text-gray-400">
{currentPage} / {totalPages}
</span>
<button
class="px-3 py-1 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
onclick={nextPage}
disabled={!hasNextPage}
>
Next
</button>
</div>
</div>
{/if} {/if}
</div> </div>
{/if} {/if}
@ -1165,30 +1135,14 @@
<!-- Pagination Controls --> <!-- Pagination Controls -->
{#if totalPages > 1} {#if totalPages > 1}
<div class="mt-4 flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"> <APagination
<div class="text-sm text-gray-600 dark:text-gray-400"> bind:currentPage
Page {currentPage} of {totalPages} ({notificationMode === "to-me" ? allToMeNotifications.length : allFromMeNotifications.length} total notifications) {totalPages}
</div> {hasNextPage}
<div class="flex items-center gap-2"> {hasPreviousPage}
<button totalItems={notificationMode === 'to-me' ? allToMeNotifications.length : allFromMeNotifications.length}
class="px-3 py-1 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed" itemsLabel="notifications"
onclick={previousPage} />
disabled={!hasPreviousPage}
>
Previous
</button>
<span class="text-sm text-gray-600 dark:text-gray-400">
{currentPage} / {totalPages}
</span>
<button
class="px-3 py-1 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
onclick={nextPage}
disabled={!hasNextPage}
>
Next
</button>
</div>
</div>
{/if} {/if}
</div> </div>
{/if} {/if}

10
src/routes/profile/+page.svelte

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Heading, P } from "flowbite-svelte"; import { Heading, P, Button } from "flowbite-svelte";
import { AAlert } from "$lib/a"; import { AAlert } from "$lib/a";
import CommentBox from "$lib/components/CommentBox.svelte"; import CommentBox from "$lib/components/CommentBox.svelte";
import CommentViewer from "$lib/components/CommentViewer.svelte"; import CommentViewer from "$lib/components/CommentViewer.svelte";
@ -8,6 +8,7 @@
import { getUserMetadata } from "$lib/utils/nostrUtils"; import { getUserMetadata } from "$lib/utils/nostrUtils";
import type { NDKEvent } from "$lib/utils/nostrUtils"; import type { NDKEvent } from "$lib/utils/nostrUtils";
import { getNdkContext } from "$lib/ndk.ts"; import { getNdkContext } from "$lib/ndk.ts";
import { goto } from "$app/navigation";
// State // State
let user = $state($userStore); let user = $state($userStore);
@ -126,11 +127,14 @@
<a href={profile.website} rel="noopener" class="text-primary-600 dark:text-primary-400 hover:underline break-all" target="_blank">{profile.website}</a> <a href={profile.website} rel="noopener" class="text-primary-600 dark:text-primary-400 hover:underline break-all" target="_blank">{profile.website}</a>
{/if} {/if}
</div> </div>
<div class="flex flex-row justify-end gap-4 text-sm">
<Button size="xs" onclick={() => goto('/profile/notifications')}>Notifications</Button>
</div>
{#if loading} {#if loading}
<div class="text-sm text-gray-500 dark:text-gray-400">Loading profile…</div> <AAlert color="primary">Loading profile…</AAlert>
{/if} {/if}
{#if error} {#if error}
<div class="text-sm text-red-600 dark:text-red-400">{error}</div> <AAlert color="red">Error loading profile: {error}</AAlert>
{/if} {/if}
</div> </div>
</div> </div>

Loading…
Cancel
Save