Merge pull request #26 from BradNut/development

Development to master
This commit is contained in:
Bradley Shellnut 2024-11-29 14:36:30 -08:00 committed by GitHub
commit 4f7ecab394
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 4874 additions and 3851 deletions

View file

@ -1,6 +1,6 @@
WALLABAG_MAX_ARTICLES=30 WALLABAG_MAX_ARTICLES=30
WALLABAG_MAX_PAGES=5 WALLABAG_MAX_PAGES=5
NODE_VERSION=18.12.1 NODE_VERSION=22
WALLABAG_CLIENT_ID='' WALLABAG_CLIENT_ID=''
WALLABAG_CLIENT_SECRET='' WALLABAG_CLIENT_SECRET=''
WALLABAG_USERNAME='' WALLABAG_USERNAME=''

View file

@ -1,3 +1,4 @@
/** @type { import("eslint").Linter.Config } */
module.exports = { module.exports = {
root: true, root: true,
extends: [ extends: [

2
.nvmrc
View file

@ -1 +1 @@
v18 v22

View file

@ -1,7 +1,9 @@
{ {
"useTabs": true, "useTabs": true,
"tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "all",
"bracketSpacing": true,
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte"], "plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."], "pluginSearchDirs": ["."],

View file

@ -15,63 +15,66 @@
"test:integration": "playwright test", "test:integration": "playwright test",
"test:unit": "vitest" "test:unit": "vitest"
}, },
"packageManager": "pnpm@9.11.0",
"devDependencies": { "devDependencies": {
"@iconify-icons/material-symbols": "^1.2.58", "@iconify-icons/material-symbols": "^1.2.58",
"@iconify-icons/mdi": "^1.2.48", "@iconify-icons/mdi": "^1.2.48",
"@iconify-icons/radix-icons": "^1.2.9", "@iconify-icons/radix-icons": "^1.2.9",
"@iconify-icons/simple-icons": "^1.2.74", "@iconify-icons/simple-icons": "^1.2.74",
"@melt-ui/pp": "^0.1.4", "@melt-ui/pp": "^0.3.2",
"@playwright/test": "^1.42.1", "@playwright/test": "^1.49.0",
"@resvg/resvg-js": "^2.6.0", "@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/enhanced-img": "^0.1.9", "@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.5.4", "@sveltejs/enhanced-img": "^0.2.1",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@sveltejs/kit": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@sveltejs/vite-plugin-svelte": "^3.1.2",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@zerodevx/svelte-img": "^2.1.0", "@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.18", "@zerodevx/svelte-img": "^2.1.2",
"eslint": "^8.57.0", "autoprefixer": "^10.4.20",
"eslint-config-prettier": "^8.10.0", "eslint": "^8.57.1",
"eslint-plugin-svelte": "^2.35.1", "eslint-config-prettier": "^9.1.0",
"iconify-icon": "^1.0.8", "eslint-plugin-svelte": "^2.46.0",
"iconify-icon": "^2.1.0",
"just-intersect": "^4.3.0", "just-intersect": "^4.3.0",
"mdsvex": "^0.11.0", "mdsvex": "^0.11.2",
"mdsvex-relative-images": "^1.0.3", "mdsvex-relative-images": "^1.0.3",
"postcss": "^8.4.37", "postcss": "^8.4.49",
"postcss-import": "^15.1.0", "postcss-import": "^16.1.0",
"postcss-load-config": "^4.0.2", "postcss-load-config": "^5.1.0",
"postcss-preset-env": "^8.5.1", "postcss-preset-env": "^9.6.0",
"prettier": "^3.2.5", "prettier": "^3.4.1",
"prettier-plugin-svelte": "^3.2.2", "prettier-plugin-svelte": "^3.3.2",
"sass": "^1.72.0", "sass": "^1.81.0",
"satori": "^0.10.13", "satori": "^0.10.14",
"satori-html": "^0.3.2", "satori-html": "^0.3.2",
"scrape-it": "^6.1.0", "scrape-it": "^6.1.3",
"sharp": "^0.32.6", "sharp": "^0.33.5",
"svelte": "^4.2.12", "svelte": "^4.2.19",
"svelte-check": "^3.6.8", "svelte-check": "^3.8.6",
"svelte-meta-tags": "^3.1.1", "svelte-meta-tags": "^3.1.4",
"svelte-preprocess": "^5.1.3", "svelte-preprocess": "^5.1.4",
"svelte-sequential-preprocessor": "^2.0.1", "svelte-sequential-preprocessor": "^2.0.2",
"tslib": "^2.6.2", "tslib": "^2.8.1",
"typescript": "^5.4.2", "typescript": "^5.7.2",
"vanilla-lazyload": "^17.9.0", "vanilla-lazyload": "^19.1.3",
"vite": "^5.1.6", "vite": "^5.4.11",
"vite-imagetools": "^5.1.2", "vite-imagetools": "^7.0.5",
"vitest": "^1.4.0" "vitest": "^1.6.0"
}, },
"type": "module", "type": "module",
"engines": {
"node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0",
"pnpm": ">=8"
},
"dependencies": { "dependencies": {
"@melt-ui/svelte": "^0.50.1", "@melt-ui/svelte": "^0.76.3",
"@sveltejs/adapter-vercel": "^5.2.0", "@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-vercel": "^5.5.0",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@vercel/og": "^0.5.20", "@vercel/og": "^0.6.4",
"ioredis": "^5.3.2", "bits-ui": "^0.21.16",
"nprogress": "^0.2.0" "flexsearch": "^0.7.43",
"ioredis": "^5.4.1",
"lucide-svelte": "^0.378.0",
"nprogress": "^0.2.0",
"svelte-local-storage-store": "^0.6.4"
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,7 @@ import {
WALLABAG_USERNAME, WALLABAG_USERNAME,
WALLABAG_PASSWORD, WALLABAG_PASSWORD,
WALLABAG_URL, WALLABAG_URL,
WALLABAG_MAX_PAGES,
PAGE_SIZE, PAGE_SIZE,
WALLABAG_MAX_ARTICLES,
USE_REDIS_CACHE USE_REDIS_CACHE
} from '$env/static/private'; } from '$env/static/private';
import intersect from 'just-intersect'; import intersect from 'just-intersect';
@ -21,14 +19,20 @@ const base: string = WALLABAG_URL;
export async function fetchArticlesApi( export async function fetchArticlesApi(
method: string, method: string,
resource: string, resource: string,
queryParams: Record<string, string>, 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 = { const pageQuery: PageQuery = {
sort: 'updated', sort: 'updated',
perPage: +queryParams?.limit || +PAGE_SIZE, perPage,
since: 0, since: 0,
page: +queryParams?.page || 1, page: Number(queryParams?.page) || 1,
tags: 'programming', tags: 'programming',
content: 'metadata' content: 'metadata'
}; };
@ -79,18 +83,18 @@ export async function fetchArticlesApi(
const cacheControl = pageResponse.headers.get('cache-control') || 'no-cache'; 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[] = []; const articles: Article[] = [];
_embedded.items.forEach((article: WallabagArticle) => { favoriteArticles.items.forEach((article: WallabagArticle) => {
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);
articles.push({ articles.push({
tags, tags,
title: article.title, title: article.title,
url: new URL(article.url), url: new URL(article.url),
domain_name: article.domain_name?.replace('www.', '') ?? '',
hashed_url: article.hashed_url, hashed_url: article.hashed_url,
reading_time: article.reading_time, reading_time: article.reading_time,
preview_picture: article.preview_picture, preview_picture: article.preview_picture,
@ -104,9 +108,9 @@ export async function fetchArticlesApi(
const responseData: ArticlePageLoad = { const responseData: ArticlePageLoad = {
articles, articles,
currentPage: page, currentPage: page,
totalPages: pages > +WALLABAG_MAX_PAGES ? +WALLABAG_MAX_PAGES : pages, totalPages: pages,
limit, limit,
totalArticles: total > +WALLABAG_MAX_ARTICLES ? +WALLABAG_MAX_ARTICLES : total, totalArticles: total,
cacheControl cacheControl
}; };

View file

@ -12,7 +12,7 @@
<h2>Favorite Articles</h2> <h2>Favorite Articles</h2>
<div class={classes.join(' ')}> <div class={classes.join(' ')}>
{#each articles as article (article.hashed_url)} {#each articles as article (article.hashed_url)}
<article class='card'> <article class="card">
<section> <section>
<h3> <h3>
<ExternalLink <ExternalLink
@ -27,6 +27,7 @@
{/if} {/if}
</ExternalLink> </ExternalLink>
</h3> </h3>
<p>{article.domain_name}</p>
</section> </section>
<section> <section>
<p>Reading time: {article.reading_time} minutes</p> <p>Reading time: {article.reading_time} minutes</p>

View file

@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import Img from '@zerodevx/svelte-img';
import type { Album } from "$lib/types/album"; import type { Album } from "$lib/types/album";
import LazyImage from '../LazyImage.svelte'; import LazyImage from './LazyImage.svelte';
export let albums: Album[]; export let albums: Album[];
const displayAlbums = const displayAlbums =

View file

@ -19,6 +19,12 @@
</a> </a>
<style lang="postcss"> <style lang="postcss">
a {
margin: 1rem 0;
padding: 0;
font-size: var(--bodyTextSize);
}
.show-icon { .show-icon {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View file

@ -0,0 +1,109 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { Pagination } from "bits-ui";
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
export let additionalClasses: string;
export let pageSize: number;
export let totalCount: number;
export let currentPage: number;
export let base: string;
</script>
<Pagination.Root let:pages count={totalCount} perPage={pageSize} page={currentPage || 1} class={`${additionalClasses}`}
onPageChange={(page) => goto(`${base}/${page}`)}>
<Pagination.PrevButton>
<ChevronLeft />
</Pagination.PrevButton>
{#each pages as page (page.key)}
{#if page.type === "ellipsis"}
<div class="ellipsis text-[15px] font-medium text-foreground-alt">...</div>
{:else}
<Pagination.Page {page}>
<a href={`${base}/${page.value}`}>
{page.value}
</a>
</Pagination.Page>
{/if}
{/each}
<Pagination.NextButton>
<ChevronRight />
</Pagination.NextButton>
</Pagination.Root>
<style lang="postcss">
:global([data-pagination-prev-button]) {
&:hover {
color: var(--shellYellow);
}
&:active {
transform: scale(0.98); /* Equivalent to active:scale-98 in Tailwind */
}
&:disabled {
cursor: not-allowed; /* Equivalent to disabled:cursor-not-allowed in Tailwind */
color: #6b7280; /* Equivalent to disabled:text-muted-foreground in Tailwind */
}
&:hover:disabled {
background-color: transparent; /* Equivalent to hover:disabled:bg-transparent in Tailwind */
}
border-right: 1px solid var(--grey);
padding: 1rem;
}
:global(.ellipsis) {
padding: 1rem;
border-right: 1px solid var(--grey);
}
:global([data-pagination-next-button]) {
&:hover {
color: var(--shellYellow);
}
&:active {
transform: scale(0.98); /* Equivalent to active:scale-98 in Tailwind */
}
&:disabled {
cursor: not-allowed; /* Equivalent to disabled:cursor-not-allowed in Tailwind */
color: #6b7280; /* Equivalent to disabled:text-muted-foreground in Tailwind */
}
&:hover:disabled {
background-color: transparent; /* Equivalent to hover:disabled:bg-transparent in Tailwind */
}
}
:global([data-selected]) {
a {
color: var(--shellYellow);
}
}
:global([data-pagination-root]) {
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;
}
:global([data-pagination-page]) {
padding: 1rem;
flex: 1;
border-right: 1px solid var(--grey);
text-decoration: none;
a {
text-decoration: none;
}
}
</style>

View file

@ -35,11 +35,6 @@
} }
} }
:global(.portfolio p) {
margin: 1rem 0;
padding: 0;
}
:global(.portfolio-details) { :global(.portfolio-details) {
margin: 0 1.5rem; margin: 0 1.5rem;

View file

@ -12,9 +12,7 @@
header { header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
//height: var(--headerHeight);
align-items: center; align-items: center;
/* color: black; */
background-color: var(--darkGrey); background-color: var(--darkGrey);
background: var(--headerBackground); background: var(--headerBackground);
box-shadow: var(--level-2); box-shadow: var(--level-2);

View file

@ -1,77 +0,0 @@
<script lang="ts">
export let additionalClasses: string;
export let pageSize: number;
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;
</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

@ -31,7 +31,7 @@
<style> <style>
@font-face { @font-face {
font-family: 'Fira Sans'; font-family: 'Fira Sans';
src: url('/src/lib/FiraSans-Bold.ttf'); src: url('/src/lib/fonts/FiraSans-Bold.ttf');
} }
.social-card { .social-card {

View file

@ -4,6 +4,7 @@ export type Article = {
tags: ArticleTag[]; tags: ArticleTag[];
title: string; title: string;
url: URL; url: URL;
domain_name: string;
hashed_url: string; hashed_url: string;
reading_time: number; reading_time: number;
preview_picture: string; preview_picture: string;
@ -16,6 +17,7 @@ export type WallabagArticle = {
tags: WallabagTag[]; tags: WallabagTag[];
title: string; title: string;
url: URL; url: URL;
domain_name: string;
hashed_url: string; hashed_url: string;
reading_time: number; reading_time: number;
preview_picture: string; preview_picture: string;

View file

@ -6,7 +6,7 @@ import type { Album } from '$lib/types/album';
import type { ArticlePageLoad } from '$lib/types/article'; import type { ArticlePageLoad } from '$lib/types/article';
export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => { export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
let baseUrl = 'https://bradleyshellnut.com'; let baseUrl;
if (url.origin.includes('prerender')) { if (url.origin.includes('prerender')) {
baseUrl = PUBLIC_SITE_URL || 'https://bradleyshellnut.com'; baseUrl = PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
} else { } else {

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; 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 Articles from '$lib/components/Articles.svelte';
import type { Album } from '$lib/types/album'; import type { Album } from '$lib/types/album';
import type { Article, ArticlePageLoad } from '$lib/types/article'; import type { Article, ArticlePageLoad } from '$lib/types/article';
@ -10,8 +10,16 @@
let articlesData: ArticlePageLoad; let articlesData: ArticlePageLoad;
let articles: Article[]; let articles: Article[];
let totalArticles: number; 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 = { const userNames = {
github: 'BradNut', github: 'BradNut',

View file

@ -1,17 +1,18 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { WALLABAG_MAX_PAGES } from '$env/static/private'; import { PAGE_SIZE } from '$env/static/private';
import { fetchArticlesApi } from '$root/routes/api'; import { fetchArticlesApi } from '$lib/api';
export async function GET({ setHeaders, url }) { export async function GET({ setHeaders, url }) {
const page = url?.searchParams?.get('page') || '1'; const page = url?.searchParams?.get('page') || '1';
if (+page > +WALLABAG_MAX_PAGES) { let limit = url?.searchParams?.get('limit') ?? PAGE_SIZE;
error(404, 'Page does not exist'); if (Number(limit) > 30) {
limit = PAGE_SIZE;
} }
try { try {
const response = await fetchArticlesApi('get', `fetchArticles`, { const response = await fetchArticlesApi('get', `fetchArticles`, {
page, page,
limit: url?.searchParams?.get('limit') || '6' limit
}); });
if (response?.articles) { if (response?.articles) {

View file

@ -1,28 +1,27 @@
import { error } from '@sveltejs/kit'; import type { MetaTagsProps } from 'svelte-meta-tags';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { WALLABAG_MAX_PAGES } from '$env/static/private';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
import type { ArticlePageLoad } from '$lib/types/article'; import type { ArticlePageLoad } from '$lib/types/article';
import type { MetaTagsProps } from 'svelte-meta-tags';
export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) => { export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) => {
const { page } = params; const { page } = params;
if (+page > +WALLABAG_MAX_PAGES) {
error(404, {
message: 'Not found'
});
}
const resp = await fetch(`/api/articles?page=${page}`); const resp = await fetch(`/api/articles?page=${page}`);
const { articles, currentPage, totalPages, limit, totalArticles, cacheControl }: ArticlePageLoad = const {
await resp.json(); articles,
currentPage,
totalPages,
limit,
totalArticles,
cacheControl,
}: ArticlePageLoad = await resp.json();
if (cacheControl?.includes('no-cache')) { if (cacheControl?.includes('no-cache')) {
setHeaders({ setHeaders({
'cache-control': cacheControl 'cache-control': cacheControl,
}); });
} else { } else {
setHeaders({ setHeaders({
'cache-control': 'max-age=43200' // 12 hours 'cache-control': 'max-age=43200', // 12 hours
}); });
} }
@ -33,7 +32,7 @@ export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) =
title: 'Favorite Articles', title: 'Favorite Articles',
description: 'My favorite articles', description: 'My favorite articles',
openGraph: { openGraph: {
title: 'Facorite Articles', title: 'Favorite Articles',
description: 'My favorite articles', description: 'My favorite articles',
url: currentPageUrl, url: currentPageUrl,
siteName: 'Bradley Shellnut Personal Website', siteName: 'Bradley Shellnut Personal Website',
@ -44,18 +43,18 @@ export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) =
url: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`, url: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
alt: `Bradley Shellnut Articles Page ${page}`, alt: `Bradley Shellnut Articles Page ${page}`,
width: 1200, width: 1200,
height: 630 height: 630,
} },
] ],
}, },
twitter: { twitter: {
title: 'Favorite Articles', title: 'Favorite Articles',
description: 'My favorite articles', description: 'My favorite articles',
card: 'summary_large_image', card: 'summary_large_image',
image: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`, image: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
imageAlt: 'Bradley Shellnut Website Logo' imageAlt: 'Bradley Shellnut Website Logo',
}, },
url: currentPageUrl url: currentPageUrl,
}); });
return { return {
@ -64,6 +63,6 @@ export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) =
totalPages, totalPages,
limit, limit,
totalArticles, totalArticles,
metaTagsChild: metaTags metaTagsChild: metaTags,
}; };
}; };

View file

@ -1,32 +1,33 @@
<script lang="ts"> <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 type { Article } from '$lib/types/article';
import Articles from "$lib/components/Articles.svelte"; import Articles from '$lib/components/Articles.svelte';
import type { PageData } from "./$types"; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;
let articles: Article[]; let articles: Article[];
let currentPage: number; let currentPage: number;
let totalArticles: number; let totalArticles: number;
let limit: number; let limit: number;
$: ({ articles, currentPage, totalPages, totalArticles, limit } = data);
$: if (data) {
({ articles, currentPage, totalArticles, limit } = data);
}
</script> </script>
<div> <h1 style:margin-bottom={"2rem"}>Favorite Tech Articles</h1>
<h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1> <Pagination
<Pagination additionalClasses="top-pagination"
additionalClasses="top-pagination" pageSize={limit}
pageSize={limit} totalCount={totalArticles}
totalCount={totalArticles} currentPage={currentPage || 1}
currentPage={currentPage || 1} base="/articles"
base="/articles" />
/> <Articles {articles} {totalArticles} classes={['columns']} />
<Articles {articles} {totalArticles} classes={['columns']} /> <Pagination
<Pagination additionalClasses="bottom-pagination"
additionalClasses="bottom-pagination" pageSize={limit}
pageSize={limit} totalCount={totalArticles}
totalCount={totalArticles} currentPage={currentPage || 1}
currentPage={currentPage || 1} base="/articles"
base="/articles" />
/>
</div>

View file

@ -44,9 +44,7 @@
loading="eager" loading="eager"
alt="Picture of Bradley Shellnut's Personal Website"> alt="Picture of Bradley Shellnut's Personal Website">
<span slot="portfolio-links"> <span slot="portfolio-links">
<p> <ExternalLink ariaLabel="View GitHub repository for my personal website" href="https://github.com/BradNut/personal-website-sveltekit" icon={GitHub} showIcon>GitHub repository</ExternalLink>
<ExternalLink ariaLabel="View GitHub repository for my personal website" href="https://github.com/BradNut/personal-website-sveltekit" icon={GitHub} showIcon>GitHub repository</ExternalLink>
</p>
</span> </span>
<PersonalWebsiteSvelteKit slot="portfolio-details" /> <PersonalWebsiteSvelteKit slot="portfolio-details" />
</Portfolio> </Portfolio>
@ -55,12 +53,8 @@
src={weddingWebsite} src={weddingWebsite}
alt="Picture of NextJS Wedding Website"> alt="Picture of NextJS Wedding Website">
<span slot="portfolio-links"> <span slot="portfolio-links">
<p> <ExternalLink ariaLabel="View Wedding Website" href="https://weddingsite-six.vercel.app/" showIcon>View Site</ExternalLink>
<ExternalLink ariaLabel="View Wedding Website" href="https://weddingsite-six.vercel.app/" showIcon>View Site</ExternalLink> <ExternalLink ariaLabel="View GitHub repository for the wedding site" href="https://github.com/BradNut/weddingsite" icon={GitHub} showIcon>GitHub repository</ExternalLink>
</p>
<p>
<ExternalLink ariaLabel="View GitHub repository for the wedding site" href="https://github.com/BradNut/weddingsite" icon={GitHub} showIcon>GitHub repository</ExternalLink>
</p>
</span> </span>
<WeddingWebsite slot="portfolio-details" /> <WeddingWebsite slot="portfolio-details" />
</Portfolio> </Portfolio>
@ -69,9 +63,7 @@
src={oldSite} src={oldSite}
alt="Home Page of the old bradleyshellnut.com website"> alt="Home Page of the old bradleyshellnut.com website">
<span slot="portfolio-links"> <span slot="portfolio-links">
<p> <ExternalLink ariaLabel="Archive of bradleyshellnut.com" href="https://web.archive.org/web/20201205233507/https://bradleyshellnut.com/about" showIcon>Link to an archive snapshot</ExternalLink>
<ExternalLink ariaLabel="Archive of bradleyshellnut.com" href="https://web.archive.org/web/20201205233507/https://bradleyshellnut.com/about" showIcon>Link to an archive snapshot</ExternalLink>
</p>
</span> </span>
<OldWebsite slot="portfolio-details" /> <OldWebsite slot="portfolio-details" />
</Portfolio> </Portfolio>
@ -82,9 +74,7 @@
src={shellnutArchitectWebsite} src={shellnutArchitectWebsite}
alt="Picture of Mark Shellnut Architect's Website"> alt="Picture of Mark Shellnut Architect's Website">
<span slot="portfolio-links"> <span slot="portfolio-links">
<p> <ExternalLink ariaLabel="View markshellnutarchitect.com" href="https://markshellnutarchitect.com" showIcon>Link to Mark Shellnut's Website</ExternalLink>
<ExternalLink ariaLabel="View markshellnutarchitect.com" href="https://markshellnutarchitect.com" showIcon>Link to Mark Shellnut's Website</ExternalLink>
</p>
</span> </span>
<MarkShellnutArchitect slot="portfolio-details" /> <MarkShellnutArchitect slot="portfolio-details" />
</Portfolio> </Portfolio>

View file

@ -1,10 +1,13 @@
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { PUBLIC_URL } from '$env/static/public'; import { PUBLIC_URL } from '$env/static/public';
import { WALLABAG_MAX_PAGES } from '$env/static/private'; import type { ArticlePageLoad } from '$lib/types/article';
const site = `https://${PUBLIC_URL}`; const site = `https://${PUBLIC_URL}`;
export const GET: RequestHandler = async function GET({ setHeaders }) { export const GET: RequestHandler = async function GET({ fetch, setHeaders }) {
const resp = await fetch(`/api/articles`);
const { totalPages }: ArticlePageLoad = await resp.json();
const xml = `<?xml version="1.0" encoding="UTF-8" ?> const xml = `<?xml version="1.0" encoding="UTF-8" ?>
<urlset <urlset
xmlns="https://www.sitemaps.org/schemas/sitemap/0.9" xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
@ -32,7 +35,7 @@ export const GET: RequestHandler = async function GET({ setHeaders }) {
<priority>1</priority> <priority>1</priority>
</url> </url>
${Array.from({ length: parseInt(WALLABAG_MAX_PAGES) }, (_, i) => { ${Array.from({ length: totalPages }, (_, i) => {
return ` return `
<url> <url>
<loc>${site}/articles/${i + 1}</loc> <loc>${site}/articles/${i + 1}</loc>

7
src/state/search.ts Normal file
View file

@ -0,0 +1,7 @@
import { persisted } from 'svelte-local-storage-store';
import { writable } from 'svelte/store';
export const searching = writable(false);
export const overlay_open = writable(false);
export const search_query = writable('');
export const search_recent = persisted<string[]>('svelte:recent-searches', []);

View file

@ -62,6 +62,7 @@
/* Cards */ /* Cards */
--cardHeightMin: 15rem; --cardHeightMin: 15rem;
--cardHeightMax: 50rem; --cardHeightMax: 50rem;
--cardWidthMax: 50rem;
--cardWidthMin: 15rem; --cardWidthMin: 15rem;
/* Media Queries - Not yet supported in CSS */ /* Media Queries - Not yet supported in CSS */
@ -74,7 +75,7 @@
} }
html { html {
background: var(--background) none repeat scroll 0% 0%; background: var(--background) none repeat scroll 0 0;
font-size: 62.5%; font-size: 62.5%;
box-sizing: border-box; box-sizing: border-box;
} }
@ -130,7 +131,7 @@ button {
cursor: pointer; cursor: pointer;
--cast: 2px; --cast: 2px;
box-shadow: var(--cast) var(--cast) 0 var(--lightAccent); 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; transition: all 0.2s;
text-decoration: none; text-decoration: none;
&:hover { &:hover {

View file

@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-vercel'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { preprocessMeltUI } from '@melt-ui/pp'; import { preprocessMeltUI } from '@melt-ui/pp';
import { mdsvex } from 'mdsvex'; import { mdsvex } from 'mdsvex';
@ -24,6 +24,9 @@ const config = {
alias: { alias: {
$root: './src' $root: './src'
} }
},
compilerOptions: {
enableSourcemap: process.env.NODE_ENV === 'development',
} }
}; };

View file

@ -5,6 +5,7 @@
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,