@@ -4,19 +4,68 @@ import Layout from "@/layouts/Layout.astro";
44
55export const prerender = false ;
66
7- // Pagination settings
7+ // Cursor-based pagination settings
88const PAGE_SIZE = 20 ;
9- const page = parseInt ( Astro .url .searchParams .get (" page " ) || " 1 " );
10- const offset = ( page - 1 ) * PAGE_SIZE ;
9+ const cursorParam = Astro .url .searchParams .get (" cursor " );
10+ const direction = Astro . url . searchParams . get ( " dir " ) || " next " ;
1111
1212// Get micro posts from D1 via content collection
1313const allPosts = (await getCollection (" micro" )).sort (
1414 (a , b ) => b .data .createdAt .valueOf () - a .data .createdAt .valueOf ()
1515);
1616
17- const posts = allPosts .slice (offset , offset + PAGE_SIZE );
18- const hasMore = allPosts .length > offset + PAGE_SIZE ;
19- const totalPosts = allPosts .length ;
17+ let posts: typeof allPosts ;
18+ let hasNext = false ;
19+ let hasPrev = false ;
20+ let nextCursor: number | null = null ;
21+ let prevCursor: number | null = null ;
22+
23+ if (! cursorParam ) {
24+ // First page - get newest posts
25+ posts = allPosts .slice (0 , PAGE_SIZE );
26+ hasNext = allPosts .length > PAGE_SIZE ;
27+ if (hasNext && posts .length > 0 ) {
28+ nextCursor = posts [posts .length - 1 ].data .createdAt .valueOf ();
29+ }
30+ } else {
31+ const cursor = parseInt (cursorParam );
32+
33+ if (direction === " prev" ) {
34+ // Previous page - get posts newer than cursor
35+ const newerPosts = allPosts .filter ((post ) => post .data .createdAt .valueOf () > cursor );
36+ // Take the last PAGE_SIZE posts from newer posts (to maintain chronological order)
37+ posts = newerPosts .slice (Math .max (0 , newerPosts .length - PAGE_SIZE ));
38+
39+ // Check if there are more newer posts
40+ hasPrev = newerPosts .length > PAGE_SIZE ;
41+ if (hasPrev && posts .length > 0 ) {
42+ prevCursor = posts [0 ].data .createdAt .valueOf ();
43+ }
44+
45+ // Check if there are older posts
46+ const olderPosts = allPosts .filter ((post ) => post .data .createdAt .valueOf () < cursor );
47+ hasNext = olderPosts .length > 0 ;
48+ if (hasNext && posts .length > 0 ) {
49+ nextCursor = posts [posts .length - 1 ].data .createdAt .valueOf ();
50+ }
51+ } else {
52+ // Next page - get posts older than cursor
53+ const olderPosts = allPosts .filter ((post ) => post .data .createdAt .valueOf () < cursor );
54+ posts = olderPosts .slice (0 , PAGE_SIZE );
55+
56+ hasNext = olderPosts .length > PAGE_SIZE ;
57+ if (hasNext && posts .length > 0 ) {
58+ nextCursor = posts [posts .length - 1 ].data .createdAt .valueOf ();
59+ }
60+
61+ // Check if there are newer posts
62+ const newerPosts = allPosts .filter ((post ) => post .data .createdAt .valueOf () > cursor );
63+ hasPrev = newerPosts .length > 0 ;
64+ if (hasPrev && posts .length > 0 ) {
65+ prevCursor = posts [0 ].data .createdAt .valueOf ();
66+ }
67+ }
68+ }
2069
2170// Format date helper
2271function formatDate(date : Date ): string {
@@ -42,10 +91,9 @@ function formatDate(date: Date): string {
4291<Layout title =" Micro - Just Be" description =" Micro blog posts from Just Be" >
4392 <header class =" mb-2" >
4493 <h1 class =" font-bold" >~/micro</h1 >
45- <p class =" text-fg-2 text-sm" >{ totalPosts } { totalPosts === 1 ? " post" : " posts" } </p >
4694 </header >
4795
48- <div id = " posts-container " class =" space-y-2" >
96+ <div class =" space-y-2" >
4997 {
5098 posts .map ((post ) => (
5199 <article class = " border-fg-2 border p-2" >
@@ -64,89 +112,33 @@ function formatDate(date: Date): string {
64112 </div >
65113
66114 {
67- hasMore && (
68- <div id = " loading-indicator" class = " text-fg-2 mt-4 text-center" >
69- Loading more...
70- </div >
115+ (hasPrev || hasNext ) && (
116+ <nav class = " mt-4 flex justify-between" >
117+ <div >
118+ { hasPrev && prevCursor ? (
119+ <a
120+ href = { ` /micro?cursor=${prevCursor }&dir=prev ` }
121+ class = " border-fg-2 hover:bg-fg-2 hover:text-bg-0 border px-2 py-1 transition-colors"
122+ >
123+ Previous
124+ </a >
125+ ) : (
126+ <span class = " border-fg-2 text-fg-2 border px-2 py-1 opacity-50" >Previous</span >
127+ )}
128+ </div >
129+ <div >
130+ { hasNext && nextCursor ? (
131+ <a
132+ href = { ` /micro?cursor=${nextCursor }&dir=next ` }
133+ class = " border-fg-2 hover:bg-fg-2 hover:text-bg-0 border px-2 py-1 transition-colors"
134+ >
135+ Next
136+ </a >
137+ ) : (
138+ <span class = " border-fg-2 text-fg-2 border px-2 py-1 opacity-50" >Next</span >
139+ )}
140+ </div >
141+ </nav >
71142 )
72143 }
73-
74- <div id =" sentinel" class =" h-px" ></div >
75144</Layout >
76-
77- <script define:vars ={ { hasMore: hasMore , pageSize: PAGE_SIZE }} >
78- if (hasMore) {
79- let currentPage = 1;
80- let isLoading = false;
81-
82- const postsContainer = document.getElementById("posts-container");
83- const loadingIndicator = document.getElementById("loading-indicator");
84- const sentinel = document.getElementById("sentinel");
85-
86- async function loadMorePosts() {
87- if (isLoading) return;
88- isLoading = true;
89-
90- if (loadingIndicator) {
91- loadingIndicator.style.display = "block";
92- }
93-
94- try {
95- currentPage++;
96- const response = await fetch(`/micro?page=${currentPage}`);
97- const html = await response.text();
98-
99- const parser = new DOMParser();
100- const doc = parser.parseFromString(html, "text/html");
101- const newPosts = doc.querySelectorAll("#posts-container > article");
102- const newHasMore = doc.getElementById("loading-indicator") !== null;
103-
104- if (postsContainer) {
105- newPosts.forEach((post) => {
106- postsContainer.appendChild(post.cloneNode(true));
107- });
108- }
109-
110- if (!newHasMore) {
111- observer.disconnect();
112- if (loadingIndicator) {
113- loadingIndicator.textContent = "No more posts";
114- }
115- }
116- } catch (error) {
117- console.error("Failed to load more posts:", error);
118- if (loadingIndicator) {
119- loadingIndicator.textContent = "Failed to load more posts";
120- }
121- } finally {
122- isLoading = false;
123- if (loadingIndicator && isLoading === false) {
124- loadingIndicator.style.display = "none";
125- }
126- }
127- }
128-
129- const observer = new IntersectionObserver(
130- (entries) => {
131- entries.forEach((entry) => {
132- if (entry.isIntersecting) {
133- loadMorePosts();
134- }
135- });
136- },
137- {
138- rootMargin: "100px",
139- }
140- );
141-
142- if (sentinel) {
143- observer.observe(sentinel);
144- }
145- }
146- </script >
147-
148- <style >
149- #loading-indicator {
150- display: none;
151- }
152- </style >
0 commit comments