Testing out pagination articles and fetching on each slug page.

This commit is contained in:
Bradley Shellnut 2023-02-09 16:25:21 -08:00
parent e3f2cae296
commit 1c1ccbbf01
20 changed files with 433 additions and 396 deletions

View file

@ -22,6 +22,7 @@
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
"@rgossiaux/svelte-headlessui": "^1.0.2", "@rgossiaux/svelte-headlessui": "^1.0.2",
"@sveltejs/adapter-auto": "^1.0.0", "@sveltejs/adapter-auto": "^1.0.0",
"@sveltejs/adapter-static": "^2.0.0",
"@sveltejs/adapter-vercel": "^1.0.5", "@sveltejs/adapter-vercel": "^1.0.5",
"@sveltejs/kit": "^1.0.0", "@sveltejs/kit": "^1.0.0",
"@types/postcss-preset-env": "^8.0.0", "@types/postcss-preset-env": "^8.0.0",

View file

@ -9,6 +9,7 @@ specifiers:
'@playwright/test': ^1.28.1 '@playwright/test': ^1.28.1
'@rgossiaux/svelte-headlessui': ^1.0.2 '@rgossiaux/svelte-headlessui': ^1.0.2
'@sveltejs/adapter-auto': ^1.0.0 '@sveltejs/adapter-auto': ^1.0.0
'@sveltejs/adapter-static': ^2.0.0
'@sveltejs/adapter-vercel': ^1.0.5 '@sveltejs/adapter-vercel': ^1.0.5
'@sveltejs/kit': ^1.0.0 '@sveltejs/kit': ^1.0.0
'@types/postcss-preset-env': ^8.0.0 '@types/postcss-preset-env': ^8.0.0
@ -48,6 +49,7 @@ devDependencies:
'@playwright/test': 1.29.2 '@playwright/test': 1.29.2
'@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.55.1 '@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.55.1
'@sveltejs/adapter-auto': 1.0.2_@sveltejs+kit@1.2.2 '@sveltejs/adapter-auto': 1.0.2_@sveltejs+kit@1.2.2
'@sveltejs/adapter-static': 2.0.0_@sveltejs+kit@1.2.2
'@sveltejs/adapter-vercel': 1.0.5_@sveltejs+kit@1.2.2 '@sveltejs/adapter-vercel': 1.0.5_@sveltejs+kit@1.2.2
'@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4 '@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4
'@types/postcss-preset-env': 8.0.0_postcss@8.4.21 '@types/postcss-preset-env': 8.0.0_postcss@8.4.21
@ -696,6 +698,14 @@ packages:
import-meta-resolve: 2.2.1 import-meta-resolve: 2.2.1
dev: true dev: true
/@sveltejs/adapter-static/2.0.0_@sveltejs+kit@1.2.2:
resolution: {integrity: sha512-M9F8EyCIdVcpZu0zvS7U7v+rc7elNNDUkMHLsOkJYlfQ9rDdif3fqteMqddcrq1lFPEGH1ErdTjpsXdb3k1e8w==}
peerDependencies:
'@sveltejs/kit': ^1.5.0
dependencies:
'@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4
dev: true
/@sveltejs/adapter-vercel/1.0.5_@sveltejs+kit@1.2.2: /@sveltejs/adapter-vercel/1.0.5_@sveltejs+kit@1.2.2:
resolution: {integrity: sha512-TnFw+2ZwUuzguIeh013h0eiFmHylBuL1aV3jcUWbJmBLRo6YA0Ti8X8VXgFP4izP7XGuLKdpUIMg7HaZe21LbA==} resolution: {integrity: sha512-TnFw+2ZwUuzguIeh013h0eiFmHylBuL1aV3jcUWbJmBLRo6YA0Ti8X8VXgFP4izP7XGuLKdpUIMg7HaZe21LbA==}
peerDependencies: peerDependencies:

View file

@ -0,0 +1,85 @@
<script lang="ts">
export let additionalClasses: string;
export let pageSize: number;
export let totalCount: number;
export let currentPage: number;
export let skip: number;
export let base: string;
// make some variables
console.log(`Total count: ${totalCount}`);
console.log(`Page Size: ${pageSize}`);
const totalPages = Math.ceil(totalCount / pageSize);
console.log(`Total Pages: ${totalPages}`)
console.log(`Current Page: ${currentPage} and type: ${typeof currentPage}`)
let prevPage = currentPage - 1;
console.log(`PrevPage: ${prevPage}`)
console.log(`Pagination current Page: ${currentPage}`)
let nextPage = currentPage + 1;
let hasNextPage = nextPage <= totalPages;
let hasPrevPage = prevPage >= 1;
console.log({ nextPage, hasNextPage, hasPrevPage })
</script>
<div class={`paginationStyles ${additionalClasses}`}>
<a
title={`${!hasPrevPage ? 'No ' : ''}Prev Page`}
aria-disabled={!hasPrevPage}
href={`${base}/${prevPage}`}
>
&#8592; <span class="word">Prev</span>
</a>
{#each { length: totalPages } as _, i}
<a
aria-current={currentPage === i + 1}
href={`${base}/${i + 1}`}
>
{i + 1}
</a>
{/each}
<a
title={`${!hasNextPage ? 'No ' : ''}Next Page`}
aria-disabled={!hasNextPage}
href={`${base}/${nextPage}`}
>
<span class="word">Next</span> &#8594;
</a>
</div>
<style lang="postcss">
a {
&[aria-current="true"] {
color: var(--shellYellow)
}
&[aria-disabled="true"] {
pointer-events: none;
color: var(--lightGrey);
text-decoration: line-through;
}
}
.paginationStyles {
display: flex;
align-content: center;
align-items: center;
justify-items: center;
border: 1px solid var(--grey);
margin: 3rem 0;
border-radius: 5px;
text-align: center;
font-size: 1.5rem;
& > * {
padding: 1rem;
flex: 1;
border-right: 1px solid var(--grey);
text-decoration: none;
}
@media (max-width: 800px) {
.word {
display: none;
}
}
}
</style>

View file

@ -6,6 +6,7 @@ const state = () => {
const { subscribe, set, update } = writable<Article[]>([]); const { subscribe, set, update } = writable<Article[]>([]);
function addAll(articles: Article[]) { function addAll(articles: Article[]) {
// console.log(typeof articles);
for (const article of articles) { for (const article of articles) {
add(article); add(article);
} }

View file

@ -1,5 +1,8 @@
export type PageQuery { export type PageQuery = {
sort: string; sort: string;
perPage: number; perPage: number;
since: number; since: number;
} page: number;
tags: string;
content: 'metadata' | 'full';
};

View file

@ -1,24 +0,0 @@
export const prerender = true;
import { type ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ isDataRequest, cookies, params, setHeaders }) => {
const queryParams = {
// ids: `${params?.id}`,
// fields:
// 'id,name,price,min_age,min_players,max_players,thumb_url,playtime,min_playtime,max_playtime,min_age,description,year_published,url,image_url'
};
const initialRequest = !isDataRequest;
console.log(`Is initialRequest: ${initialRequest}`);
const cacheValue = `${initialRequest ? +new Date() : cookies.get('articles-cache')}`;
console.log(`Cache Value: ${cacheValue}`);
if (initialRequest) {
cookies.set('articles-cache', cacheValue, { path: '/', httpOnly: false });
}
return {
articlesCacheBust: cacheValue
};
};

View file

@ -1,101 +0,0 @@
<script lang="ts">
import 'iconify-icon';
import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer/index.svelte';
import '$root/styles/styles.pcss';
import Analytics from '$lib/components/analytics/index.svelte';
import SEO from '$lib/components/SEO.svelte';
const dev = process.env.NODE_ENV !== 'production';
</script>
{#if !dev}
<Analytics />
{/if}
<SEO />
<div class="wrapper">
<Header />
<main>
<slot />
</main>
<Footer />
</div>
<style lang="postcss">
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
h3 {
color: white;
}
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
main {
flex: 1;
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 2rem 0rem;
max-width: 85vw;
@media (min-width: 1600px) {
max-width: 70vw;
}
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
:global(p) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
:global(li) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
</style>

3
src/routes/+layout.ts Normal file
View file

@ -0,0 +1,3 @@
<script lang="ts">export const prerender = true;</script>;
export const load: PageLoad = async () => {};

View file

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from "./$types";
import SEO from '$root/lib/components/SEO.svelte'; import SEO from '$root/lib/components/SEO.svelte';
export let data: PageData; // export let data: PageData;
const userNames = { const userNames = {
github: 'BradNut', github: 'BradNut',
linkedIn: 'bradley-shellnut', linkedIn: 'bradley-shellnut',

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -3,7 +3,9 @@ import {
WALLABAG_CLIENT_SECRET, WALLABAG_CLIENT_SECRET,
WALLABAG_USERNAME, WALLABAG_USERNAME,
WALLABAG_PASSWORD, WALLABAG_PASSWORD,
WALLABAG_URL WALLABAG_URL,
WALLABAG_MAX_ARTICLES,
PAGE_SIZE
} from '$env/static/private'; } from '$env/static/private';
import intersect from 'just-intersect'; import intersect from 'just-intersect';
import type { Article, WallabagArticle } from '$root/lib/types/article'; import type { Article, WallabagArticle } from '$root/lib/types/article';
@ -39,8 +41,11 @@ export async function fetchArticlesApi(
const pageQuery: PageQuery = { const pageQuery: PageQuery = {
sort: 'updated', sort: 'updated',
perPage: 500, perPage: +PAGE_SIZE,
since: 0 since: 0,
page: +queryParams?.page || 1,
tags: 'programming',
content: 'metadata'
}; };
const entriesQueryParams = new URLSearchParams(pageQuery); const entriesQueryParams = new URLSearchParams(pageQuery);
console.log(`Entries params: ${entriesQueryParams}`); console.log(`Entries params: ${entriesQueryParams}`);
@ -63,17 +68,17 @@ export async function fetchArticlesApi(
throw new Error(pageResponse.statusText); throw new Error(pageResponse.statusText);
} }
const entries = await pageResponse.json(); const { _embedded, page, pages, total, limit } = await pageResponse.json();
const articles: Article[] = []; const articles: Article[] = [];
// do { // do {
// nbEntries += entries._embedded.items.length; // nbEntries += entries._embedded.items.length;
console.log(`number of articles fetched: ${entries._embedded.items.length}`); console.log(`number of articles fetched: ${_embedded.items.length}`);
entries._embedded.items.forEach((article: WallabagArticle) => { _embedded.items.forEach((article: WallabagArticle) => {
if (articles?.length === 30) { // if (articles?.length === +WALLABAG_MAX_ARTICLES) {
console.log('Reached 30 articles'); // console.log('Reached 30 articles');
return; // return;
} // }
const rawTags = article?.tags?.map((tag) => tag.slug); const rawTags = article?.tags?.map((tag) => tag.slug);
if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) { if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) {
const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag); const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag);
@ -105,5 +110,5 @@ export async function fetchArticlesApi(
// entries = await response.json(); // entries = await response.json();
// } while (entries._links.next); // } while (entries._links.next);
return { articles }; return { articles, currentPage: page, totalPages: pages, limit, totalArticles: total };
} }

View file

@ -2,25 +2,27 @@ import { json } from '@sveltejs/kit';
import type { RequestHandler, RequestEvent } from './$types'; import type { RequestHandler, RequestEvent } from './$types';
import { fetchArticlesApi } from '$root/routes/api'; import { fetchArticlesApi } from '$root/routes/api';
export const GET: RequestHandler = async ({ url, setHeaders, request, params }: RequestEvent) => { export const GET: RequestHandler = async ({ url, setHeaders }: RequestEvent) => {
try { try {
const response = await fetchArticlesApi('get', `search`, {}); const response = await fetchArticlesApi('get', `fetchArticles`, {
page: url?.searchParams?.get('page') || '1'
});
if (response?.articles) { if (response?.articles) {
setHeaders({ setHeaders({
'cache-control': 'max-age=604800' 'cache-control': 'max-age=60'
}); });
const articlesResponse = response.articles; // const articlesResponse = response.articles;
console.log(`Found articles ${articlesResponse.length}`); // console.log(`Found articles ${articlesResponse?.articles?.length}`);
const articles = []; // const articles = [];
for (const article of articlesResponse) { // for (const article of articlesResponse) {
const { tags, title, url, hashed_url, reading_time, preview_picture } = article; // const { tags, title, url, hashed_url, reading_time, preview_picture } = article;
articles.push({ tags, title, url, hashed_url, reading_time, preview_picture }); // articles.push({ tags, title, url, hashed_url, reading_time, preview_picture });
} // }
return json(articles); return json(response);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { page } from "$app/stores";
console.log(`error page ${JSON.stringify($page)}`)
</script>
{#if $page.status === 404}
<h1>Sorry page not found! 🤷🏼</h1>
<p>You just hit a route that doesn't exist.</p>
{/if}
{#if $page.status === 500}
<h1>Sorry an unexpected error occurred! 😿</h1>
<p>Please try again later. </p>
{/if}

View file

@ -1,17 +0,0 @@
import { getCurrentCookieValue } from '$lib/util/cookieUtils';
import type { Article } from '$root/lib/types/article';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, parent, url, setHeaders }) => {
const parentData = await parent();
const cacheBust = getCurrentCookieValue('articles-cache') || parentData.articlesCacheBust;
// const search = url.searchParams.get('search') || '';
const resp = await fetch(`/api/articles?cache=${cacheBust}`);
const articles: Article[] = await resp.json();
return {
articles
};
};

View file

@ -1,30 +1,43 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/stores"; import { page } from "$app/stores";
import { Article } from "$lib/types/article"; import { articleStore } from "$lib/stores/articleStore";
import { ArticleTag } from "$lib/types/articleTag"; import { ArticleTag } from "$lib/types/articleTag";
import Pagination from "$lib/components/pagination/index.svelte";
import SEO from "$root/lib/components/SEO.svelte"; import SEO from "$root/lib/components/SEO.svelte";
import type { Article } from "$root/lib/types/article";
import type { PageData } from "./$types";
$: ({ articles } = $page.data); export let data: PageData;
let articles: Article[];
$: ({ articles, currentPage, totalPages, totalArticles, limit } = data);
// let currentPage: number;
// let perPage: number;
// let maxPages: number;
// $: ({ currentPage, perPage, maxPages } = $page?.data);
// console.log('Articles Page params', $page?.params);
// console.log('Articles Page', { currentPage, perPage, maxPages });
// console.log(`Page data: ${JSON.stringify(data)}`)
// console.log(`Article Page Path Slug ${$page.params.page}`);
// console.log(`Article Page Current Page: ${currentPage}`)
// $: start = (currentPage - 1) * perPage;
// $: skip = currentPage * perPage;
// console.log(`Article Store size: ${$articleStore.length}`);
const currentPage: number = 1; // $: articles = $articleStore.slice(start, start + perPage);
// const articles = data?.articles?.nodes;
// const maxArticles = parseInt(process.env.GATSBY_WALLABAG_MAX_ARTICLES);
// const totalCount =
// data.articles.totalCount > maxArticles ? maxArticles : articles.length;
</script> </script>
<SEO title={`Tech Articles - Page ${currentPage}`} /> <SEO title={`Tech Articles - Page ${$page?.params?.page}`} />
<div class="pageStyles"> <div class="pageStyles">
<h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1> <h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1>
<!-- <Pagination <Pagination
clazz="top-pagination" additionalClasses="top-pagination"
pageSize={parseInt(process.env.GATSBY_PAGE_SIZE)} pageSize={limit}
totalCount={totalCount} totalCount={totalArticles}
currentPage={pageContext.currentPage || 1} currentPage={currentPage || 1}
skip={pageContext.skip} skip={page}
base="/articles" base="/articles"
/> --> />
<div class="articlesStyles"> <div class="articlesStyles">
{#each articles as article (article.hashed_url)} {#each articles as article (article.hashed_url)}
<div class="articleStyles card"> <div class="articleStyles card">
@ -33,7 +46,7 @@
<a <a
target="_blank" target="_blank"
aria-label={`Link to ${article.title}`} aria-label={`Link to ${article.title}`}
href={article.url} href={article.url.href}
rel="noreferrer" rel="noreferrer"
> >
{article.title} {article.title}
@ -52,14 +65,14 @@
</div> </div>
{/each} {/each}
</div> </div>
<!-- <Pagination <Pagination
clazz="bottom-pagination" additionalClasses="bottom-pagination"
pageSize={parseInt(process.env.GATSBY_PAGE_SIZE)} pageSize={limit}
totalCount={totalCount} totalCount={totalPages}
currentPage={pageContext.currentPage || 1} currentPage={currentPage || 1}
skip={pageContext.skip} skip={page}
base="/articles" base="/articles"
/> --> />
</div> </div>
<style lang="postcss"> <style lang="postcss">
@ -95,6 +108,9 @@
display: grid; display: grid;
grid-template-rows: 1fr auto; grid-template-rows: 1fr auto;
align-items: start; align-items: start;
a {
cursor: pointer;
}
/* p { /* p {
margin: 0.4rem 0.25rem; margin: 0.4rem 0.25rem;

View file

@ -0,0 +1,43 @@
import type { PageLoad } from './$types';
import type { Article } from '$root/lib/types/article';
export const load: PageLoad = async ({ fetch, params }) => {
const { page } = params;
const resp = await fetch(`/api/articles?page=${page}`);
const {
articles,
currentPage,
totalPages,
limit,
totalArticles
}: {
articles: Article[];
currentPage: number;
totalPages: number;
limit: number;
totalArticles: number;
} = await resp.json();
return {
articles,
currentPage,
totalPages,
limit,
totalArticles
};
// console.log(`Page: ${page}`);
// try {
// if (page && +page <= 5 && +page > 0) {
// return {
// currentPage: +page,
// perPage: parentData?.perPage,
// maxPages: parentData?.totalPages
// };
// } else {
// console.log('Page load error 404');
// throw error(404, 'Not found');
// }
// } catch (e) {
// console.error(e);
// throw error(500, 'Error');
// }
};

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -5,7 +5,6 @@
<SEO title="Privacy Blog" /> <SEO title="Privacy Blog" />
<div> <div>
<div>
<h1>Privacy</h1> <h1>Privacy</h1>
<p> <p>
Long story short, I believe everyone should know who has your personal Long story short, I believe everyone should know who has your personal
@ -38,8 +37,8 @@
and/or set your browser to send "Do Not Track" requests as I honor and/or set your browser to send "Do Not Track" requests as I honor
them. them.
</p> </p>
</div> </div>
<div style="margin-top: 2.5rem;"> <div style="margin-top: 2.5rem;">
<h2>Useful Resources</h2> <h2>Useful Resources</h2>
<p> <p>
Here are a few sites/lists of privacy oriented software for you to Here are a few sites/lists of privacy oriented software for you to
@ -77,8 +76,8 @@
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div> <div>
<h3>Privacy Centric Paid Services I use:</h3> <h3>Privacy Centric Paid Services I use:</h3>
<ul> <ul>
<li> <li>
@ -137,8 +136,8 @@
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div> <div>
<h3>NAS Servers for Self Hosting:</h3> <h3>NAS Servers for Self Hosting:</h3>
<ul> <ul>
<li> <li>
@ -186,8 +185,8 @@
once it is more mature. once it is more mature.
</li> </li>
</ul> </ul>
</div> </div>
<div> <div>
<h3>Software Deployed:</h3> <h3>Software Deployed:</h3>
<ul> <ul>
<li> <li>
@ -213,8 +212,10 @@
: An open source file synchronization program. : An open source file synchronization program.
</li> </li>
</ul> </ul>
</div>
</div> </div>
<style lang="postcss"> <style lang="postcss">
div {
margin-bottom: 2rem;
}
</style> </style>

View file

@ -1 +0,0 @@
export const prerender = true;