Browse Source

Unbreak pagination

master
Nuša Pukšič 6 months ago committed by buttercat1791
parent
commit
e667cbd3cb
  1. 90
      src/lib/components/Notifications.svelte
  2. 5
      src/lib/components/ZettelEditor.svelte
  3. 300
      src/lib/components/publications/Publication.svelte
  4. 2
      src/lib/components/util/ArticleNav.svelte
  5. 4
      src/routes/new/compose/+page.svelte
  6. 2
      src/routes/profile/notifications/+page.svelte

90
src/lib/components/Notifications.svelte

@ -70,7 +70,7 @@
let allFromMeNotifications = $state<NDKEvent[]>([]); // All fetched "from-me" notifications let allFromMeNotifications = $state<NDKEvent[]>([]); // All fetched "from-me" notifications
let allPublicMessages = $state<NDKEvent[]>([]); // All fetched public messages let allPublicMessages = $state<NDKEvent[]>([]); // All fetched public messages
let currentPage = $state(1); let currentPage = $state(1);
let itemsPerPage = 20; // Show 20 items per page let itemsPerPage = 10; // Show 20 items per page
let hasFetchedToMe = $state(false); // Track if we've already fetched "to-me" data let hasFetchedToMe = $state(false); // Track if we've already fetched "to-me" data
let hasFetchedFromMe = $state(false); // Track if we've already fetched "from-me" data let hasFetchedFromMe = $state(false); // Track if we've already fetched "from-me" data
let hasFetchedPublic = $state(false); // Track if we've already fetched public messages let hasFetchedPublic = $state(false); // Track if we've already fetched public messages
@ -650,27 +650,13 @@
} }
} }
// AI-NOTE: Pagination navigation functions // Pagination navigation
function nextPage() { $effect (() => {
if (hasNextPage) { console.log(`[Pagination] Mode: ${notificationMode}, Current Page: ${currentPage}, Total Pages: ${totalPages}`);
currentPage++; updateDisplayedItems();
updateDisplayedItems(); // scroll to top
} window.scrollTo({ top: 0, behavior: 'smooth' });
} });
function previousPage() {
if (hasPreviousPage) {
currentPage--;
updateDisplayedItems();
}
}
function goToPage(page: number) {
if (page >= 1 && page <= totalPages) {
currentPage = page;
updateDisplayedItems();
}
}
// AI-NOTE: Update displayed items based on current page // AI-NOTE: Update displayed items based on current page
function updateDisplayedItems() { function updateDisplayedItems() {
@ -845,36 +831,33 @@
</script> </script>
{#if isOwnProfile && $userStore.signedIn} {#if isOwnProfile && $userStore.signedIn}
<div class="mb-6 w-full"> <Heading tag="h3" class="h-leather">Notifications</Heading>
<div class="flex items-center justify-between mb-4">
<Heading tag="h3" class="h-leather">Notifications</Heading> <div class="flex flex-row items-center gap-3">
<!-- New Message Button -->
<div class="flex flex-row items-center gap-3"> <Button
<!-- New Message Button --> color="primary"
<Button size="sm"
color="primary" onclick={() => openNewMessageModal()}
size="sm" class="flex !mb-0 items-center gap-1.5 px-3 py-1.5 text-sm font-medium"
onclick={() => openNewMessageModal()} >
class="flex !mb-0 items-center gap-1.5 px-3 py-1.5 text-sm font-medium" New Message
</Button>
<!-- Mode toggle -->
<div class="flex flex-row bg-gray-300 dark:bg-gray-700 rounded-lg p-1">
{#each modes as mode}
{@const modeLabel = mode === "to-me" ? "To Me" : mode === "from-me" ? "From Me" : "Public Messages"}
<button
class={`mode-toggle-button px-3 py-1 text-sm !mb-0 font-medium rounded-md ${notificationMode === mode ? 'active' : 'inactive'}`}
onclick={() => setNotificationMode(mode)}
> >
New Message {modeLabel}
</Button> </button>
{/each}
<!-- Mode toggle -->
<div class="flex flex-row bg-gray-300 dark:bg-gray-700 rounded-lg p-1">
{#each modes as mode}
{@const modeLabel = mode === "to-me" ? "To Me" : mode === "from-me" ? "From Me" : "Public Messages"}
<button
class={`mode-toggle-button px-3 py-1 text-sm !mb-0 font-medium rounded-md ${notificationMode === mode ? 'active' : 'inactive'}`}
onclick={() => setNotificationMode(mode)}
>
{modeLabel}
</button>
{/each}
</div>
</div> </div>
</div> </div>
{#if loading} {#if loading}
<div class="flex items-center justify-center py-8 min-h-96"> <div class="flex items-center justify-center py-8 min-h-96">
<div class="notifications-loading-spinner rounded-full h-8 w-8 border-b-2 border-primary-600"></div> <div class="notifications-loading-spinner rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
@ -1038,11 +1021,11 @@
{/if} {/if}
{:else} {:else}
{#if notifications.length === 0} {#if notifications.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 notifications {notificationMode === "to-me" ? "received" : "sent"} found.</P> <P>No notifications {notificationMode === "to-me" ? "received" : "sent"} found.</P>
</div> </AAlert>
{:else} {:else}
<div class="max-h-[72rem] overflow-y-auto overflow-x-hidden space-y-4"> <div class="space-y-4">
{#each notifications.slice(0, 100) as notification} {#each notifications.slice(0, 100) as notification}
{@const authorProfile = authorProfiles.get(notification.pubkey)} {@const authorProfile = authorProfiles.get(notification.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"> <div class="message-container p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
@ -1147,8 +1130,7 @@
</div> </div>
{/if} {/if}
{/if} {/if}
</div>
<!-- New Message Modal --> <!-- New Message Modal -->
<Modal bind:open={showNewMessageModal} size="lg" class="w-full"> <Modal bind:open={showNewMessageModal} size="lg" class="w-full">
<div class="modal-content p-6"> <div class="modal-content p-6">

5
src/lib/components/ZettelEditor.svelte

@ -53,7 +53,8 @@
ta.selectionStart = ta.selectionEnd = pos; ta.selectionStart = ta.selectionEnd = pos;
}); });
} }
const newNoteTemplate = `\n== Note Title\n:author: \n:version: 1.0\n:published_on: 2024-01-01\n:published_by: Alexandria\n:summary: \n:tags: \n:image: \n\nNote content here...\n`; const today = new Date().toISOString().split('T')[0];
const newNoteTemplate = `\n== Note Title\n:author: \n:version: 1.0\n:published_on: ${today}\n:published_by: Alexandria\n:summary: \n:tags: \n:image: \n\nNote content here...\n`;
function insertNoteTemplate() { function insertNoteTemplate() {
const ta = getTextarea(); const ta = getTextarea();
if (!ta || ta.disabled) return; if (!ta || ta.disabled) return;
@ -191,7 +192,7 @@
</div> </div>
{:else} {:else}
<!-- Informative text about ZettelEditor purpose --> <!-- Informative text about ZettelEditor purpose -->
<AAlert color="blue" classes="max-w-lg self-center"> <AAlert color="blue" classes="w-full self-center">
{#snippet title()}Note-Taking Tool{/snippet} {#snippet title()}Note-Taking Tool{/snippet}
<p class="text-sm mb-3"> <p class="text-sm mb-3">
This editor is for creating individual notes (30041 events) only. Each section becomes a separate note event. This editor is for creating individual notes (30041 events) only. Each section becomes a separate note event.

300
src/lib/components/publications/Publication.svelte

@ -23,6 +23,7 @@
import type { SveltePublicationTree } from "./svelte_publication_tree.svelte"; import type { SveltePublicationTree } from "./svelte_publication_tree.svelte";
import TableOfContents from "./TableOfContents.svelte"; import TableOfContents from "./TableOfContents.svelte";
import type { TableOfContents as TocType } from "./table_of_contents.svelte"; import type { TableOfContents as TocType } from "./table_of_contents.svelte";
import ArticleNav from "$components/util/ArticleNav.svelte";
let { rootAddress, publicationType, indexEvent, publicationTree, toc } = $props<{ let { rootAddress, publicationType, indexEvent, publicationTree, toc } = $props<{
rootAddress: string; rootAddress: string;
@ -252,164 +253,169 @@
</script> </script>
<!-- Add gap & items-start so sticky sidebars size correctly --> <!-- Add gap & items-start so sticky sidebars size correctly -->
<div class="grid grid-cols-[1fr_3fr_1fr] gap-4 items-start"> <div class="grid gap-4 items-start grid-cols-[1fr_3fr_1fr] grid-rows-[auto_1fr]">
<!-- Table of contents --> <!-- Full-width ArticleNav row -->
{#if publicationType !== "blog" && !isLeaf} <ArticleNav
<Sidebar publicationType={publicationType}
class="z-10 ml-4 bg-transparent sticky top-24 max-h-[calc(100vh-6rem)] overflow-y-auto rootId={indexEvent.id}
border border-s-4 rounded border-primary-200 dark:border-primary-800 indexEvent={indexEvent}
dark:bg-primary-1000" />
position="static" <!-- Three-column row -->
activeUrl={`#${activeAddress ?? ""}`} <div class="contents">
classes={{ <!-- Table of contents -->
div: 'bg-transparent', /* inner wrapper neutral */ {#if publicationType !== "blog" && !isLeaf}
active: 'bg-primary-100 dark:bg-primary-800 p-2 rounded-lg', <Sidebar
nonactive: 'bg-primary-50 dark:bg-primary-800', class="z-10 ml-4 bg-transparent sticky top-[162px] h-[calc(100vh-165px)] overflow-y-auto
}} border border-s-4 rounded border-primary-200 dark:border-primary-800
> dark:bg-primary-1000"
<CloseButton activeUrl={`#${activeAddress ?? ""}`}
onclick={closeTocSidebar} classes={{
class="btn-leather hover:bg-primary-50 dark:hover:bg-primary-800" div: 'bg-transparent',
/> active: 'bg-primary-100 dark:bg-primary-800 p-2 rounded-lg',
<TableOfContents nonactive: 'bg-primary-50 dark:bg-primary-800',
{rootAddress}
{toc}
depth={2}
onSectionFocused={(address: string) => publicationTree.setBookmark(address)}
onLoadMore={() => {
if (!isLoading && !isDone && publicationTree) {
loadMore(4);
}
}} }}
/>
</Sidebar>
{/if}
<div>
<!-- Default publications -->
{#if $publicationColumnVisibility.main}
<!-- Remove overflow-auto so page scroll drives it -->
<div class="flex flex-col p-4 space-y-4 max-w-2xl flex-grow-2">
<div
class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"
>
<Details event={indexEvent} />
</div>
<!-- Publication sections/cards -->
{#each leaves as leaf, i}
{#if leaf == null}
<Alert class="flex space-x-2">
<ExclamationCircleOutline class="w-5 h-5" />
Error loading content. One or more events could not be loaded.
</Alert>
{:else}
{@const address = leaf.tagAddress()}
<PublicationSection
{rootAddress}
{leaves}
{address}
{publicationTree}
{toc}
ref={(el) => onPublicationSectionMounted(el, address)}
/>
{/if}
{/each}
<div class="flex justify-center my-4">
{#if isLoading}
<Button disabled color="primary">Loading...</Button>
{:else if !isDone}
<Button color="primary" onclick={() => loadMore(1)}>Show More</Button>
{:else}
<p class="text-gray-500 dark:text-gray-400">
You've reached the end of the publication.
</p>
{/if}
</div>
</div>
{/if}
<!-- Blog list -->
{#if $publicationColumnVisibility.blog}
<!-- Remove overflow-auto -->
<div
class={`flex flex-col p-4 space-y-4 max-w-xl flex-grow-1 ${isInnerActive() ? "discreet" : ""}`}
> >
<div <SidebarWrapper>
class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border" <TableOfContents
> {rootAddress}
<Details event={indexEvent} /> {toc}
</div> depth={2}
<!-- List blog excerpts --> onSectionFocused={(address: string) => publicationTree.setBookmark(address)}
{#each leaves as leaf, i} onLoadMore={() => {
{#if leaf} if (!isLoading && !isDone && publicationTree) {
<BlogHeader loadMore(4);
rootId={leaf.tagAddress()} }
event={leaf} }}
onBlogUpdate={loadBlog}
active={!isInnerActive()}
/> />
{/if} </SidebarWrapper>
{/each} </Sidebar>
</div>
{/if} {/if}
{#if isInnerActive()} <div class="mt-[70px]">
{#key currentBlog} <!-- Default publications -->
<!-- Remove overflow-auto & sticky; allow page scroll --> {#if $publicationColumnVisibility.main}
<div class="flex flex-col p-4 max-w-3xl flex-grow-2"> <!-- Remove overflow-auto so page scroll drives it -->
<!-- ...existing code... --> <div class="flex flex-col p-4 space-y-4 max-w-2xl flex-grow-2">
<div
class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"
>
<Details event={indexEvent} />
</div>
<!-- Publication sections/cards -->
{#each leaves as leaf, i}
{#if leaf == null}
<Alert class="flex space-x-2">
<ExclamationCircleOutline class="w-5 h-5" />
Error loading content. One or more events could not be loaded.
</Alert>
{:else}
{@const address = leaf.tagAddress()}
<PublicationSection
{rootAddress}
{leaves}
{address}
{publicationTree}
{toc}
ref={(el) => onPublicationSectionMounted(el, address)}
/>
{/if}
{/each}
<div class="flex justify-center my-4">
{#if isLoading}
<Button disabled color="primary">Loading...</Button>
{:else if !isDone}
<Button color="primary" onclick={() => loadMore(1)}>Show More</Button>
{:else}
<p class="text-gray-500 dark:text-gray-400">
You've reached the end of the publication.
</p>
{/if}
</div>
</div> </div>
{/key} {/if}
{/if}
</div>
{#if $publicationColumnVisibility.discussion} <!-- Blog list -->
<Sidebar {#if $publicationColumnVisibility.blog}
position="static" <!-- Remove overflow-auto -->
class="sticky top-24 max-h-[calc(100vh-6rem)] overflow-y-auto bg-primary-50 dark:bg-primary-1000 <div
border border-primary-200 dark:border-primary-800 rounded" class={`flex flex-col p-4 space-y-4 max-w-xl flex-grow-1 ${isInnerActive() ? "discreet" : ""}`}
> >
<SidebarWrapper> <div
<SidebarGroup> class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"
<div class="flex justify-between items-baseline"> >
<Heading tag="h1" class="h-leather !text-lg">Discussion</Heading> <Details event={indexEvent} />
<Button
class="btn-leather hidden sm:flex z-30 !p-1 bg-primary-50 dark:bg-gray-800"
outline
onclick={closeDiscussion}
>
<CloseOutline />
</Button>
</div> </div>
<div class="flex flex-col space-y-4"> <!-- List blog excerpts -->
<!-- TODO {#each leaves as leaf, i}
alternative for other publications and {#if leaf}
when blog is not opened, but discussion is opened from the list
-->
{#if showBlogHeader() && currentBlog && currentBlogEvent}
<BlogHeader <BlogHeader
rootId={currentBlog} rootId={leaf.tagAddress()}
event={currentBlogEvent} event={leaf}
onBlogUpdate={loadBlog} onBlogUpdate={loadBlog}
active={true} active={!isInnerActive()}
/> />
{/if} {/if}
<div class="flex flex-col w-full space-y-4"> {/each}
<Card class="ArticleBox card-leather w-full grid max-w-xl"> </div>
<div class="flex flex-col my-2"> {/if}
<span>Unknown</span>
<span class="text-gray-500">1.1.1970</span>
</div>
<div class="flex flex-col flex-grow space-y-4">
This is a very intelligent comment placeholder that applies to
all the content equally well.
</div>
</Card>
</div>
</div>
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
{/if}
{#if isInnerActive()}
{#key currentBlog}
<!-- Remove overflow-auto & sticky; allow page scroll -->
<div class="flex flex-col p-4 max-w-3xl flex-grow-2">
<!-- ...existing code... -->
</div>
{/key}
{/if}
</div>
{#if $publicationColumnVisibility.discussion}
<Sidebar
class="z-10 ml-4 bg-transparent sticky top-[162px] h-[calc(100vh-165px)] overflow-y-auto
border border-s-4 rounded border-primary-200 dark:border-primary-800
dark:bg-primary-1000"
>
<SidebarWrapper>
<SidebarGroup>
<div class="flex justify-between items-baseline">
<Heading tag="h1" class="h-leather !text-lg">Discussion</Heading>
<Button
class="btn-leather hidden sm:flex z-30 !p-1 bg-primary-50 dark:bg-gray-800"
outline
onclick={closeDiscussion}
>
<CloseOutline />
</Button>
</div>
<div class="flex flex-col space-y-4">
<!-- TODO
alternative for other publications and
when blog is not opened, but discussion is opened from the list
-->
{#if showBlogHeader() && currentBlog && currentBlogEvent}
<BlogHeader
rootId={currentBlog}
event={currentBlogEvent}
onBlogUpdate={loadBlog}
active={true}
/>
{/if}
<div class="flex flex-col w-full space-y-4">
<Card class="ArticleBox card-leather w-full grid max-w-xl">
<div class="flex flex-col my-2">
<span>Unknown</span>
<span class="text-gray-500">1.1.1970</span>
</div>
<div class="flex flex-col flex-grow space-y-4">
This is a very intelligent comment placeholder that applies to
all the content equally well.
</div>
</Card>
</div>
</div>
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
{/if}
</div>
</div> </div>

2
src/lib/components/util/ArticleNav.svelte

@ -152,7 +152,7 @@
</script> </script>
<nav <nav
class="Navbar navbar-leather flex fixed top-[100px] sm:top-[92px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10 transition-transform duration-300 {isVisible class="Navbar navbar-leather col-span-3 flex fixed top-[100px] sm:top-[92px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10 transition-transform duration-300 {isVisible
? 'translate-y-0' ? 'translate-y-0'
: '-translate-y-full'}" : '-translate-y-full'}"
> >

4
src/routes/new/compose/+page.svelte

@ -130,8 +130,8 @@
<title>Compose Note - Alexandria</title> <title>Compose Note - Alexandria</title>
</svelte:head> </svelte:head>
<!-- Main container with 75% width and centered --> <!-- Main container with max 1024px width and centered -->
<div class="flex flex-col self-center items-center w-full px-2 space-y-4"> <div class="flex flex-col self-center items-center w-full max-w-[1024px] mx-auto px-2 space-y-4">
<Heading <Heading
tag="h1" class="h-leather mb-2"> tag="h1" class="h-leather mb-2">
Compose Notes Compose Notes

2
src/routes/profile/notifications/+page.svelte

@ -4,7 +4,7 @@ import { AAlert } from "$lib/a";
import { userStore } from "$lib/stores/userStore"; import { userStore } from "$lib/stores/userStore";
</script> </script>
<div class="w-full max-w-3xl mx-auto mt-10 px-4"> <div class="flex flex-col w-full max-w-3xl mx-auto px-2 items-center gap-4">
{#if $userStore?.signedIn} {#if $userStore?.signedIn}
<Notifications /> <Notifications />
{:else} {:else}

Loading…
Cancel
Save