mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
Using BitsUI pagination component for Articles and modifying articles fetching.
This commit is contained in:
parent
442f36f18d
commit
61e01522f5
13 changed files with 358 additions and 304 deletions
22
package.json
22
package.json
|
|
@ -21,14 +21,14 @@
|
|||
"@iconify-icons/radix-icons": "^1.2.9",
|
||||
"@iconify-icons/simple-icons": "^1.2.74",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@playwright/test": "^1.44.0",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@sveltejs/adapter-static": "^3.0.1",
|
||||
"@sveltejs/enhanced-img": "^0.2.0",
|
||||
"@sveltejs/kit": "^2.5.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@sveltejs/enhanced-img": "^0.2.1",
|
||||
"@sveltejs/kit": "^2.5.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@zerodevx/svelte-img": "^2.1.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
"postcss": "^8.4.38",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
"postcss-preset-env": "^9.5.11",
|
||||
"postcss-preset-env": "^9.5.14",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"sass": "^1.77.0",
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
"satori-html": "^0.3.2",
|
||||
"scrape-it": "^6.1.2",
|
||||
"sharp": "^0.33.3",
|
||||
"svelte": "^4.2.16",
|
||||
"svelte": "^4.2.17",
|
||||
"svelte-check": "^3.7.1",
|
||||
"svelte-meta-tags": "^3.1.2",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
|
|
@ -57,17 +57,17 @@
|
|||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vanilla-lazyload": "^19.1.3",
|
||||
"vite": "^5.2.11",
|
||||
"vite": "^5.2.12",
|
||||
"vite-imagetools": "^7.0.2",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@melt-ui/svelte": "^0.76.3",
|
||||
"@sveltejs/adapter-vercel": "^5.3.0",
|
||||
"@sveltejs/adapter-vercel": "^5.3.1",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"bits-ui": "^0.21.7",
|
||||
"bits-ui": "^0.21.10",
|
||||
"flexsearch": "^0.7.43",
|
||||
"ioredis": "^5.4.1",
|
||||
"lucide-svelte": "^0.378.0",
|
||||
|
|
|
|||
496
pnpm-lock.yaml
496
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -4,9 +4,7 @@ import {
|
|||
WALLABAG_USERNAME,
|
||||
WALLABAG_PASSWORD,
|
||||
WALLABAG_URL,
|
||||
WALLABAG_MAX_PAGES,
|
||||
PAGE_SIZE,
|
||||
WALLABAG_MAX_ARTICLES,
|
||||
USE_REDIS_CACHE
|
||||
} from '$env/static/private';
|
||||
import intersect from 'just-intersect';
|
||||
|
|
@ -24,11 +22,18 @@ export async function fetchArticlesApi(
|
|||
queryParams: Record<string, string>,
|
||||
data?: Record<string, unknown>
|
||||
) {
|
||||
let perPage = Number(queryParams?.limit);
|
||||
if (perPage > 30) {
|
||||
perPage = Number(PAGE_SIZE);
|
||||
} else {
|
||||
perPage = Number(queryParams?.limit);
|
||||
}
|
||||
|
||||
const pageQuery: PageQuery = {
|
||||
sort: 'updated',
|
||||
perPage: +queryParams?.limit || +PAGE_SIZE,
|
||||
perPage,
|
||||
since: 0,
|
||||
page: +queryParams?.page || 1,
|
||||
page: Number(queryParams?.page) || 1,
|
||||
tags: 'programming',
|
||||
content: 'metadata'
|
||||
};
|
||||
|
|
@ -79,18 +84,18 @@ export async function fetchArticlesApi(
|
|||
|
||||
const cacheControl = pageResponse.headers.get('cache-control') || 'no-cache';
|
||||
|
||||
const { _embedded, page, pages, total, limit } = await pageResponse.json();
|
||||
const { _embedded: favoriteArticles, page, pages, total, limit } = await pageResponse.json();
|
||||
const articles: Article[] = [];
|
||||
|
||||
_embedded.items.forEach((article: WallabagArticle) => {
|
||||
favoriteArticles.items.forEach((article: WallabagArticle) => {
|
||||
const rawTags = article?.tags?.map((tag) => tag.slug);
|
||||
if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) {
|
||||
const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag);
|
||||
|
||||
articles.push({
|
||||
tags,
|
||||
title: article.title,
|
||||
url: new URL(article.url),
|
||||
domain_name: article.domain_name?.replace('www.', '') ?? '',
|
||||
hashed_url: article.hashed_url,
|
||||
reading_time: article.reading_time,
|
||||
preview_picture: article.preview_picture,
|
||||
|
|
@ -104,9 +109,9 @@ export async function fetchArticlesApi(
|
|||
const responseData: ArticlePageLoad = {
|
||||
articles,
|
||||
currentPage: page,
|
||||
totalPages: pages > +WALLABAG_MAX_PAGES ? +WALLABAG_MAX_PAGES : pages,
|
||||
totalPages: pages,
|
||||
limit,
|
||||
totalArticles: total > +WALLABAG_MAX_ARTICLES ? +WALLABAG_MAX_ARTICLES : total,
|
||||
totalArticles: total,
|
||||
cacheControl
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<h2>Favorite Articles</h2>
|
||||
<div class={classes.join(' ')}>
|
||||
{#each articles as article (article.hashed_url)}
|
||||
<article class='card'>
|
||||
<article class="card">
|
||||
<section>
|
||||
<h3>
|
||||
<ExternalLink
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
{/if}
|
||||
</ExternalLink>
|
||||
</h3>
|
||||
<p>{article.domain_name}</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>Reading time: {article.reading_time} minutes</p>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<script lang="ts">
|
||||
import Img from '@zerodevx/svelte-img';
|
||||
import type { Album } from "$lib/types/album";
|
||||
import LazyImage from '../LazyImage.svelte';
|
||||
|
||||
import LazyImage from './LazyImage.svelte';
|
||||
|
||||
export let albums: Album[];
|
||||
const displayAlbums =
|
||||
|
|
@ -8,15 +8,6 @@
|
|||
export let totalCount: number;
|
||||
export let currentPage: number;
|
||||
export let base: string;
|
||||
|
||||
// make some variables
|
||||
$: totalPages = Math.ceil(totalCount / pageSize);
|
||||
$: prevPage = currentPage - 1;
|
||||
$: nextPage = currentPage + 1;
|
||||
$: hasNextPage = nextPage <= totalPages;
|
||||
$: hasPrevPage = prevPage >= 1;
|
||||
|
||||
$: console.log(hasPrevPage, hasNextPage, prevPage, nextPage, currentPage, totalPages, pageSize);
|
||||
</script>
|
||||
|
||||
<Pagination.Root let:pages count={totalCount} perPage={pageSize} page={currentPage || 1} class={`${additionalClasses}`}
|
||||
|
|
@ -26,7 +17,7 @@
|
|||
</Pagination.PrevButton>
|
||||
{#each pages as page (page.key)}
|
||||
{#if page.type === "ellipsis"}
|
||||
<div class="text-[15px] font-medium text-foreground-alt">...</div>
|
||||
<div class="ellipsis text-[15px] font-medium text-foreground-alt">...</div>
|
||||
{:else}
|
||||
<Pagination.Page {page}>
|
||||
<a href={`${base}/${page.value}`}>
|
||||
|
|
@ -63,6 +54,11 @@
|
|||
padding: 1rem;
|
||||
}
|
||||
|
||||
:global(.ellipsis) {
|
||||
padding: 1rem;
|
||||
border-right: 1px solid var(--grey);
|
||||
}
|
||||
|
||||
:global([data-pagination-next-button]) {
|
||||
&:hover {
|
||||
color: var(--shellYellow);
|
||||
|
|
@ -4,6 +4,7 @@ export type Article = {
|
|||
tags: ArticleTag[];
|
||||
title: string;
|
||||
url: URL;
|
||||
domain_name: string;
|
||||
hashed_url: string;
|
||||
reading_time: number;
|
||||
preview_picture: string;
|
||||
|
|
@ -16,6 +17,7 @@ export type WallabagArticle = {
|
|||
tags: WallabagTag[];
|
||||
title: string;
|
||||
url: URL;
|
||||
domain_name: string;
|
||||
hashed_url: string;
|
||||
reading_time: number;
|
||||
preview_picture: string;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { Album } from '$lib/types/album';
|
|||
import type { ArticlePageLoad } from '$lib/types/article';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
|
||||
let baseUrl = 'https://bradleyshellnut.com';
|
||||
let baseUrl;
|
||||
if (url.origin.includes('prerender')) {
|
||||
baseUrl = PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import Bandcamp from '$lib/components/bandcamp/index.svelte';
|
||||
import Bandcamp from '$lib/components/Bandcamp.svelte';
|
||||
import Articles from '$lib/components/Articles.svelte';
|
||||
import type { Album } from '$lib/types/album';
|
||||
import type { Article, ArticlePageLoad } from '$lib/types/article';
|
||||
|
|
@ -10,8 +10,16 @@
|
|||
let articlesData: ArticlePageLoad;
|
||||
let articles: Article[];
|
||||
let totalArticles: number;
|
||||
$: ({ albums, articlesData } = data);
|
||||
$: ({ articles, totalArticles } = articlesData);
|
||||
|
||||
$: if (data) {
|
||||
albums = data.albums;
|
||||
articlesData = data.articlesData;
|
||||
}
|
||||
|
||||
$: if (articlesData) {
|
||||
articles = articlesData.articles;
|
||||
totalArticles = articlesData.totalArticles;
|
||||
}
|
||||
|
||||
const userNames = {
|
||||
github: 'BradNut',
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { json, error } from '@sveltejs/kit';
|
||||
import { WALLABAG_MAX_PAGES } from '$env/static/private';
|
||||
import { PAGE_SIZE, WALLABAG_MAX_PAGES } from '$env/static/private';
|
||||
import { fetchArticlesApi } from '$lib/api';
|
||||
|
||||
export async function GET({ setHeaders, url }) {
|
||||
const page = url?.searchParams?.get('page') || '1';
|
||||
if (+page > +WALLABAG_MAX_PAGES) {
|
||||
error(404, 'Page does not exist');
|
||||
// if (+page > +WALLABAG_MAX_PAGES) {
|
||||
// error(404, 'Page does not exist');
|
||||
// }
|
||||
let limit = url?.searchParams?.get('limit') ?? PAGE_SIZE;
|
||||
if (Number(limit) > 30) {
|
||||
limit = PAGE_SIZE;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchArticlesApi('get', `fetchArticles`, {
|
||||
page,
|
||||
limit: url?.searchParams?.get('limit') || '6'
|
||||
limit
|
||||
});
|
||||
|
||||
if (response?.articles) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { WALLABAG_MAX_PAGES } from '$env/static/private';
|
||||
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||
import type { ArticlePageLoad } from '$lib/types/article';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) => {
|
||||
const { page } = params;
|
||||
if (+page > +WALLABAG_MAX_PAGES) {
|
||||
error(404, {
|
||||
message: 'Not found',
|
||||
});
|
||||
}
|
||||
// if (+page > +WALLABAG_MAX_PAGES) {
|
||||
// error(404, {
|
||||
// message: 'Not found',
|
||||
// });
|
||||
// }
|
||||
const resp = await fetch(`/api/articles?page=${page}`);
|
||||
const { articles, currentPage, totalPages, limit, totalArticles, cacheControl }: ArticlePageLoad =
|
||||
await resp.json();
|
||||
const {
|
||||
articles,
|
||||
currentPage,
|
||||
totalPages,
|
||||
limit,
|
||||
totalArticles,
|
||||
cacheControl,
|
||||
}: ArticlePageLoad = await resp.json();
|
||||
|
||||
if (cacheControl?.includes('no-cache')) {
|
||||
setHeaders({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import Pagination from '$lib/components/Pagination.svelte';
|
||||
import type { Article } from '$lib/types/article';
|
||||
import Articles from '$lib/components/Articles.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
|
@ -9,26 +9,25 @@
|
|||
let currentPage: number;
|
||||
let totalArticles: number;
|
||||
let limit: number;
|
||||
$: ({ articles, currentPage, totalPages, totalArticles, limit } = data);
|
||||
|
||||
$: console.log(currentPage, totalPages, totalArticles, limit);
|
||||
$: if (data) {
|
||||
({ articles, currentPage, totalArticles, limit } = data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1>
|
||||
<Pagination
|
||||
<h1 style:margin-bottom={"2rem"}>Favorite Tech Articles</h1>
|
||||
<Pagination
|
||||
additionalClasses="top-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
<Articles {articles} {totalArticles} classes={['columns']} />
|
||||
<Pagination
|
||||
/>
|
||||
<Articles {articles} {totalArticles} classes={['columns']} />
|
||||
<Pagination
|
||||
additionalClasses="bottom-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
|
|
@ -62,6 +62,7 @@
|
|||
/* Cards */
|
||||
--cardHeightMin: 15rem;
|
||||
--cardHeightMax: 50rem;
|
||||
--cardWidthMax: 50rem;
|
||||
--cardWidthMin: 15rem;
|
||||
|
||||
/* Media Queries - Not yet supported in CSS */
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
}
|
||||
|
||||
html {
|
||||
background: var(--background) none repeat scroll 0% 0%;
|
||||
background: var(--background) none repeat scroll 0 0;
|
||||
font-size: 62.5%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
|
@ -130,7 +131,7 @@ button {
|
|||
cursor: pointer;
|
||||
--cast: 2px;
|
||||
box-shadow: var(--cast) var(--cast) 0 var(--lightAccent);
|
||||
text-shadow: 0.5px 0.5px 0 rgba(0, 0, 0, 0.2);
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
|
|
|
|||
Loading…
Reference in a new issue