const feedList: FeedSchemaWithUnread[] = await db.raw(`
select
f.*,
- count(fe.feed_id) - sum(fe.is_read) as unread
+ sum(fe.is_read) as read,
+ count(fe.feed_id) as total
from feeds f
join feed_entry fe on fe.feed_id = f.id
group by fe.feed_id
});
app.get('/feeds/:feed_id', async (req, res) => {
+ const page = parseInt(req.query?.page?.toString() ?? '1') - 1;
+
const feedEntries = await db.select('*').from('feed_entry').where({
feed_id: req.params.feed_id
- }).orderBy('pub_date', 'desc').limit(100);
+ }).orderBy('pub_date', 'desc').limit(100).offset(page * 100);
+
+ const feedData: FeedSchemaWithUnread[] = await db.raw(`
+select
+ f.*,
+ sum(fe.is_read) as read,
+ count(fe.feed_id) as total
+from feeds f
+join feed_entry fe on fe.feed_id = f.id
+where f.id = ?
+group by fe.feed_id
+`, [req.params.feed_id]);
if(req.accepts('html')) {
- res.send(renderReaderAppFeedEntries(feedEntries))
+ res.send(renderReaderAppFeedEntries(page, feedData.pop(), feedEntries))
return;
}
res.json(feedEntries);
export function renderReaderAppFeedListItem(feed: FeedSchemaWithUnread, autoload: boolean = false): string {
return `<a href="/feeds/${feed.id}" hx-get="feeds/${feed.id}" hx-trigger="click${autoload? ', load': ''}" hx-target=".feed-entries" class="feed" data-id="${feed.id}">
-${feed.title} <span class="unread-count" id="unread-${feed.id}">(${feed.unread})</span>
+${feed.title} <span class="unread-count" id="unread-${feed.id}">(${feed.total - feed.read})</span>
</a>`;
}
}
-export function renderReaderAppFeedEntries(list: FeedWithEntrySchema[]): string {
- return list.map(renderReaderAppFeedEntry).join("\n");
+export function renderPagination(baseLink: string, currentPage: number, totalItems: number, itemsPerPage: number = 100): string {
+ let pageLinks: string[] = ['<p class="align-right">'];
+ const pages = Math.ceil(totalItems/itemsPerPage);
+ for(let i = 1; i <= pages; ++i) {
+ if(i === (currentPage + 1)) {
+ pageLinks.push(i.toString());
+ }
+ else {
+ pageLinks.push(`<a href="#" hx-get="${baseLink}?page=${i}" hx-trigger="click" hx-target=".feed-entries"><button>${i}</button></a>`);
+ }
+ }
+
+ pageLinks.push('</p>');
+ return pageLinks.join("\n");
+}
+
+export function renderReaderAppFeedEntries(page: number, feed: FeedSchemaWithUnread, list: FeedWithEntrySchema[]): string {
+ return `
+ <p>
+ <strong>${feed.title}</strong><br>
+ <code>${feed.url}</code>
+ </p>
+ ${list.map(renderReaderAppFeedEntry).join("\n")}
+ ${renderPagination(`/feeds/${feed.id}`, page, feed.total)}
+ `;
}