Merge pull request #22 from BradNut/development

Development
This commit is contained in:
Bradley Shellnut 2023-12-15 23:58:49 +00:00 committed by GitHub
commit dc983df55c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 766 additions and 816 deletions

View file

@ -1,20 +1,30 @@
module.exports = { module.exports = {
root: true, root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], plugins: ['@typescript-eslint'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: { parserOptions: {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2020 ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
}, },
env: { env: {
browser: true, browser: true,
es2017: true, es2017: true,
node: true node: true
} },
}; overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

View file

@ -2,6 +2,7 @@
"cSpell.words": [ "cSpell.words": [
"Bandcamp", "Bandcamp",
"bradleyshellnut", "bradleyshellnut",
"clazz",
"iconify", "iconify",
"Mullvad", "Mullvad",
"nextjs", "nextjs",

View file

@ -23,10 +23,11 @@
"@melt-ui/pp": "^0.1.4", "@melt-ui/pp": "^0.1.4",
"@playwright/test": "^1.40.1", "@playwright/test": "^1.40.1",
"@resvg/resvg-js": "^2.6.0", "@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-static": "^2.0.3", "@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/adapter-vercel": "^1.0.6", "@sveltejs/adapter-vercel": "^4.0.0",
"@sveltejs/enhanced-img": "^0.1.5", "@sveltejs/enhanced-img": "^0.1.6",
"@sveltejs/kit": "^1.27.7", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.62.0",
"@zerodevx/svelte-img": "^2.1.0", "@zerodevx/svelte-img": "^2.1.0",
@ -51,16 +52,15 @@
"sharp": "^0.32.6", "sharp": "^0.32.6",
"svelte": "^4.2.8", "svelte": "^4.2.8",
"svelte-check": "^3.6.2", "svelte-check": "^3.6.2",
"svelte-lazy-loader": "^1.0.0",
"svelte-meta-tags": "^3.1.0", "svelte-meta-tags": "^3.1.0",
"svelte-preprocess": "^5.1.1", "svelte-preprocess": "^5.1.2",
"svelte-sequential-preprocessor": "^2.0.1", "svelte-sequential-preprocessor": "^2.0.1",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vanilla-lazyload": "^17.8.5", "vanilla-lazyload": "^17.8.5",
"vite": "^4.5.1", "vite": "^5.0.0",
"vite-imagetools": "^5.1.2", "vite-imagetools": "^5.1.2",
"vitest": "^0.32.4" "vitest": "^1.0.0"
}, },
"type": "module", "type": "module",
"engines": { "engines": {
@ -74,4 +74,4 @@
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"nprogress": "^0.2.0" "nprogress": "^0.2.0"
} }
} }

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -6,11 +6,12 @@
export let href: string; export let href: string;
export let ariaLabel: string; export let ariaLabel: string;
export let showIcon: boolean = false; export let showIcon: boolean = false;
export let clazz = "";
export let icon: IconifyIcon = OpenInNew; export let icon: IconifyIcon = OpenInNew;
</script> </script>
<a class:show-icon={showIcon} aria-label={`Open ${ariaLabel} externally`} title={`Open ${ariaLabel} externally`} {href} {rel} {target}> <a class:show-icon={showIcon} class={clazz} aria-label={`Open ${ariaLabel} externally`} title={`Open ${ariaLabel} externally`} {href} {rel} {target}>
<slot /> <slot />
{#if showIcon} {#if showIcon}
<iconify-icon {icon} width="24" height="24" role="img" title={`Open ${ariaLabel} Externally`} /> <iconify-icon {icon} width="24" height="24" role="img" title={`Open ${ariaLabel} Externally`} />

View file

@ -23,11 +23,6 @@
] ]
} }
} }
// [
// { format: 'avif', src: `${album.artwork}`, width: 230, height: 230 },
// { format: 'webp', src: `${album.artwork}`, width: 230, height: 230 },
// { format: 'jpg', src: `${album.artwork}`, width: 230, height: 230 }
// ]
} }
</script> </script>

View file

@ -6,7 +6,9 @@ import { html as toReactNode } from 'satori-html';
import firaSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf'; import firaSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf';
import { dev } from '$app/environment'; import { dev } from '$app/environment';
export async function componentToPng(component, props, height, width) { export async function componentToPng(component,
props: Record<string, string | undefined>,
height: number, width: number) {
const result = component.render(props); const result = component.render(props);
const markup = toReactNode(`${result.html}<style lang="css">${result.css.code}</style>`); const markup = toReactNode(`${result.html}<style lang="css">${result.css.code}</style>`);

View file

@ -1,9 +1,12 @@
export type BandCampResults = {
collectionItems: Album[];
}
export type Album = { export type Album = {
url: string; url: string;
artwork: string; artwork: string;
title: string; title: string;
artist: string; artist: string;
src?: ExternalImageSource[];
}; };
export type ExternalImageSource = { export type ExternalImageSource = {

View file

@ -29,3 +29,12 @@ export type WallabagTag = {
label: string; label: string;
slug: string; slug: string;
}; };
export type ArticlePageLoad = {
articles: Article[];
currentPage: number;
totalPages: number;
limit: number;
totalArticles: number;
cacheControl: string;
};

12
src/lib/types/courses.ts Normal file
View file

@ -0,0 +1,12 @@
export type Course = {
name: string,
externalLinks: ExternalLink[],
tags: string[]
}
export type ExternalLink = {
ariaLabel: string,
href: string,
showIcon: boolean,
text: string
}

View file

@ -4,14 +4,12 @@ import { fetchBandcampAlbums } from './fetchBandcampAlbums';
describe('test fetchBandcampAlbums', () => { describe('test fetchBandcampAlbums', () => {
it('fetches bandcamp albums', async () => { it('fetches bandcamp albums', async () => {
const albums = await fetchBandcampAlbums(); const albums = await fetchBandcampAlbums();
console.log('albums');
expect(albums).not.toBeNull(); expect(albums).not.toBeNull();
expect(albums).toBeTruthy(); expect(albums).toBeTruthy();
expect(albums?.length).toBeGreaterThan(0); expect(albums?.length).toBeGreaterThan(0);
for (const album of albums) { for (const album of albums) {
expect(album?.artist).toHaveLength; expect(album?.artist).toHaveLength;
expect(album?.artwork).toHaveLength; expect(album?.artwork).toHaveLength;
expect(album?.src).toHaveLength;
expect(album?.title).toHaveLength; expect(album?.title).toHaveLength;
expect(album?.url).toHaveLength; expect(album?.url).toHaveLength;
} }

View file

@ -2,7 +2,7 @@ import { BANDCAMP_USERNAME, USE_REDIS_CACHE } from '$env/static/private';
import scrapeIt from 'scrape-it'; import scrapeIt from 'scrape-it';
import type { ScrapeResult } from 'scrape-it'; import type { ScrapeResult } from 'scrape-it';
import { redis } from '$lib/server/redis'; import { redis } from '$lib/server/redis';
import type { Album } from '../types/album'; import type { Album, BandCampResults } from '../types/album';
export async function fetchBandcampAlbums() { export async function fetchBandcampAlbums() {
try { try {
@ -18,7 +18,7 @@ export async function fetchBandcampAlbums() {
} }
} }
const { data }: ScrapeResult<Album[]> = await scrapeIt( const { data }: ScrapeResult<BandCampResults> = await scrapeIt(
`https://bandcamp.com/${BANDCAMP_USERNAME}`, `https://bandcamp.com/${BANDCAMP_USERNAME}`,
{ {
collectionItems: { collectionItems: {
@ -55,5 +55,6 @@ export async function fetchBandcampAlbums() {
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return [];
} }
} }

View file

@ -2,6 +2,8 @@ import type { MetaTagsProps } from 'svelte-meta-tags';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { fetchBandcampAlbums } from '$lib/util/fetchBandcampAlbums'; import { fetchBandcampAlbums } from '$lib/util/fetchBandcampAlbums';
import type { Album } from '$lib/types/album';
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 = 'https://bradleyshellnut.com';
@ -41,8 +43,10 @@ export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
url: currentPageUrl url: currentPageUrl
}); });
const albums = async () => await fetchBandcampAlbums(); const [albums, articles]: [Album[], ArticlePageLoad] = await Promise.all([
const articles = async () => await fetch(`/api/articles?page=1&limit=3`); await fetchBandcampAlbums(),
(await fetch(`/api/articles?page=1&limit=3`)).json()
]);
setHeaders({ setHeaders({
'cache-control': 'max-age=43200' 'cache-control': 'max-age=43200'
@ -50,7 +54,7 @@ export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => {
return { return {
baseUrl, baseUrl,
metaTagsChild: metaTags, metaTagsChild: metaTags,
albums: albums(), albums,
articlesData: (await articles()).json() articlesData: articles
}; };
}; };

View file

@ -3,8 +3,7 @@
import Bandcamp from '$lib/components/bandcamp/index.svelte'; import Bandcamp from '$lib/components/bandcamp/index.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 } from '$lib/types/article'; import type { Article, ArticlePageLoad } from '$lib/types/article';
import type { ArticlePageLoad } from './articles/[page]/+page.server';
export let data: PageData; export let data: PageData;
let albums: Album[]; let albums: Album[];

View file

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import Img from '@zerodevx/svelte-img';
import Graphql from '@iconify-icons/simple-icons/graphql'; import Graphql from '@iconify-icons/simple-icons/graphql';
import Nextdotjs from '@iconify-icons/simple-icons/nextdotjs'; import Nextdotjs from '@iconify-icons/simple-icons/nextdotjs';
import Prisma from '@iconify-icons/simple-icons/prisma'; import Prisma from '@iconify-icons/simple-icons/prisma';
@ -10,12 +9,16 @@
import Svelte from '@iconify-icons/simple-icons/svelte'; import Svelte from '@iconify-icons/simple-icons/svelte';
import TypeScript from '@iconify-icons/simple-icons/typescript'; import TypeScript from '@iconify-icons/simple-icons/typescript';
import LazyImage from '$lib/components/LazyImage.svelte'; import LazyImage from '$lib/components/LazyImage.svelte';
import adventure from '$lib/assets/images/adventure.png?as=run:0'; import rural from '$lib/assets/images/rural.png?as=run:0';
import tortie_derp from '$lib/assets/images/tortie_derp.jpg?as=run'; import tortie_derp from '$lib/assets/images/tortie_derp.jpg?as=run';
import orange_derp from '$lib/assets/images/orange_derp.jpg?as=run'; import orange_derp from '$lib/assets/images/orange_derp.jpg?as=run';
import turnip from '$lib/assets/images/turnip.svg'; import turnip from '$lib/assets/images/turnip.svg';
import Tag from '$lib/components/Tag.svelte'; import CourseCard from './CourseCard.svelte';
import ExternalLink from '$lib/components/ExternalLink.svelte'; import courseData from './course.json';
import type { Course } from '$root/lib/types/courses';
import TechListItem from './TechListItem.svelte';
const courses: Course[] = courseData.courses;
</script> </script>
<div class="about"> <div class="about">
@ -45,99 +48,70 @@
At home I delve into other frameworks, languages, and platforms such At home I delve into other frameworks, languages, and platforms such
as: as:
</p> </p>
<div <div class="tech-list">
class="tech-list" <TechListItem
> itemText="React"
<a ariaLabel="React"
target="_blank"
aria-label="React"
href="https://reactjs.org/" href="https://reactjs.org/"
class="center" clazz="center"
rel="noreferrer" icon={React}
> />
<iconify-icon icon={React} width="24" height="24" role="img" title="React" /> <TechListItem
<p>React</p> itemText="TypeScript"
</a> ariaLabel="TypeScript"
<a href="https://www.typescriptlang.org/"
target="_blank" clazz="center"
aria-label="TypeScript" icon={TypeScript}
href="https://typescriptlang.org/" />
class="center" <TechListItem
rel="noreferrer" itemText="Svelte"
> ariaLabel="Svelte"
<iconify-icon icon={TypeScript} width="24" height="24" role="img" title="TypeScript" />
<p>TypeScript</p>
</a>
<a
target="_blank"
aria-label="Svelte"
href="https://svelte.dev" href="https://svelte.dev"
class="center" clazz="center"
rel="noreferrer" icon={Svelte}
> />
<iconify-icon icon={Svelte} width="24" height="24" role="img" title="Svelte" /> <TechListItem
<p>Svelte</p> itemText="NextJS"
</a> ariaLabel="NextJS"
<a
target="_blank"
aria-label="NextJS"
href="https://nextjs.org/" href="https://nextjs.org/"
class="center" clazz="center"
rel="noreferrer" icon={Nextdotjs}
> />
<iconify-icon icon={Nextdotjs} width="24" height="24" role="img" title="NextJS" /> <TechListItem
<p>NextJS</p> itemText="Remix"
</a> ariaLabel="Remix"
<a
target="_blank"
aria-label="Remix"
href="https://remix.run/" href="https://remix.run/"
class="center" clazz="center"
rel="noreferrer" icon={Remix}
> />
<iconify-icon icon={Remix} width="24" height="24" role="img" title="Remix" /> <TechListItem
<p>Remix</p> itemText="GraphQL"
</a> ariaLabel="GraphQL"
<a
target="_blank"
aria-label="GraphQL"
href="https://graphql.org/" href="https://graphql.org/"
class="center" clazz="center"
rel="noreferrer" icon={Graphql}
> />
<iconify-icon icon={Graphql} width="24" height="24" role="img" title="GraphQL" /> <TechListItem
<p>GraphQL</p> itemText="Prisma"
</a> ariaLabel="Prisma"
<a
target="_blank"
aria-label="Prisma"
href="https://prisma.io/" href="https://prisma.io/"
class="center" clazz="center"
rel="noreferrer" icon={Prisma}
> />
<iconify-icon icon={Prisma} width="24" height="24" role="img" title="Prisma" /> <TechListItem
<p>Prisma</p> itemText="GatsbyJS"
</a> ariaLabel="GatsbyJS"
<a
target="_blank"
aria-label="GatsbyJS"
href="https://gatsbyjs.com/" href="https://gatsbyjs.com/"
class="center" clazz="center"
rel="noreferrer" icon={Gatsby}
> />
<iconify-icon icon={Gatsby} width="24" height="24" role="img" title="Gatsby" /> <TechListItem
<p>Gatsby</p> itemText="Docker"
</a> ariaLabel="Docker"
<a
target="_blank"
aria-label="Docker"
href="https://docker.com/" href="https://docker.com/"
class="center" clazz="center"
rel="noreferrer" icon={Docker}
> />
<iconify-icon icon={Docker} width="24" height="24" role="img" title="Docker" />
<p>Docker</p>
</a>
</div> </div>
</div> </div>
<div> <div>
@ -147,113 +121,16 @@
those below: those below:
</p> </p>
<div class="extracurricular"> <div class="extracurricular">
<div class="card"> {#each courses as course}
<h3> <CourseCard {course} />
<ExternalLink {/each}
ariaLabel="Wes Bos Courses"
href="https://wesbos.com/courses"
showIcon
>
Wes Bos
</ExternalLink>
</h3>
<div class="tags">
<Tag name="React" />
<Tag name="GraphQL" />
<Tag name="Gatsby" />
<Tag name="JavaScript" />
</div>
</div>
<div class="card">
<h3>
<ExternalLink
ariaLabel="Scott Tolinski"
href="https://www.scotttolinski.com"
showIcon
>
Scott Tolinski
</ExternalLink>
<ExternalLink
ariaLabel="Levelup Tutorials"
href="https://levelup.video"
showIcon
>
Level Up Tutorials
</ExternalLink>
</h3>
<div class="tags">
<Tag name="React" />
<Tag name="TypeScript" />
<Tag name="Svelte Kit" />
<Tag name="Remix" />
<Tag name="Figma" />
<Tag name="Design Systems" />
</div>
</div>
<div class="card">
<h3>
<ExternalLink
ariaLabel="Amy Kapernick"
href="https://www.amyskapers.dev/"
showIcon
>
Amy Kapernick
</ExternalLink>
<ExternalLink
ariaLabel="Levelup Tutorials"
href="https://levelup.video"
showIcon
>
Level Up Tutorials
</ExternalLink>
</h3>
<div class="tags">
<Tag name="Accessibility for Everyone" />
</div>
</div>
<div class="card">
<h3>
<ExternalLink
ariaLabel="Andrew Mead on Udemy"
href="https://www.udemy.com/user/andrewmead/"
showIcon
>
Andrew Mead
</ExternalLink>
</h3>
<div class="tags">
<Tag name="GraphQL" />
<Tag name="Apollo" />
<Tag name="Prisma" />
</div>
</div>
<div class="card">
<h3>
<ExternalLink
ariaLabel="Steven Grider on Udemy"
href="https://www.udemy.com/user/sgslo/"
showIcon
>
Steven Grider
</ExternalLink>
</h3>
<div class="tags">
<Tag name="React" />
<Tag name="Redux" />
<Tag name="Docker" />
<Tag name="GraphQL" />
<Tag name="CSS" />
<Tag name="HTML" />
<Tag name="JavaScript" />
</div>
</div>
</div> </div>
</div> </div>
<div> <div>
<h2>Other fun things about me&hellip;</h2> <h2>Other fun things about me&hellip;</h2>
<div style="display: grid;"> <div style="display: grid;">
<p> <p>
Currently traveling around the world! Living it up in Mountain View
</p> </p>
<div <div
style=" style="
@ -263,16 +140,16 @@
justify-content: center; justify-content: center;
" "
> >
<LazyImage src={adventure} alt="Clip art of the car traveling in the forest" /> <LazyImage src={rural} alt="Clip art of house near trees" />
<p class="center">Traveling around</p> <p class="center">Mountain View</p>
</div> </div>
</div> </div>
<div> <div>
<p>Bringing these two cats, Turnip and Taco, along for the ride.</p> <p>Hanging out with these two cats, Turnip and Taco.</p>
<div class="cat-pics"> <div class="cat-pics">
<figure> <figure>
<LazyImage src={tortie_derp} alt="Turnip Cat" /> <LazyImage src={tortie_derp} alt="Turnip Cat" />
<p class="center">Turnip <img class="icon" src={turnip} width="25px" height="25px" alt="Turnip" /></p> <p class="center">Turnip <img class="icon" src={String(turnip)} width="25px" height="25px" alt="Turnip" /></p>
</figure> </figure>
<figure> <figure>
<LazyImage src={orange_derp} alt="Taco Cat" /> <LazyImage src={orange_derp} alt="Taco Cat" />
@ -300,7 +177,6 @@
& p { & p {
margin: 1rem; margin: 1rem;
/* padding: 0.2rem; */
} }
} }
@ -312,32 +188,6 @@
gap: 1rem; gap: 1rem;
margin-top: 1rem; margin-top: 1rem;
font-size: 2rem; font-size: 2rem;
& a {
display: grid;
justify-items: center;
font-weight: bold;
margin-right: 0;
padding: 0;
text-decoration: none;
padding: 0.3rem;
margin-left: 1rem;
color: var(--lightGrey);
& p {
font-size: 1.5rem;
padding-top: 0.3rem;
margin: 0;
}
&:hover {
color: var(--shellYellow);
& p {
color: var(--shellYellow);
}
}
}
} }
.extracurricular { .extracurricular {
@ -346,10 +196,6 @@
place-content: center; place-content: center;
gap: 1.5rem; gap: 1.5rem;
.card {
max-width: 30rem;
}
@media (max-width: 1000px) { @media (max-width: 1000px) {
grid-template-columns: repeat(2, auto); grid-template-columns: repeat(2, auto);
--cardHeightMin: 20rem; --cardHeightMin: 20rem;
@ -361,13 +207,6 @@
} }
} }
.tags {
display: flex;
flex-wrap: wrap;
justify-content: left;
align-items: center;
}
.cat-pics { .cat-pics {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(200px, 0.3fr)); grid-template-columns: repeat(2, minmax(200px, 0.3fr));

View file

@ -0,0 +1,40 @@
<script lang="ts">
import ExternalLink from "$lib/components/ExternalLink.svelte";
import Tag from "$lib/components/Tag.svelte";
import type { Course } from "$lib/types/courses";
export let course: Course;
const { externalLinks, tags } = course;
</script>
<div class="card">
<h3>
{#each externalLinks as link}
<ExternalLink
ariaLabel={link.ariaLabel}
href={link.href}
showIcon={link.showIcon}
>
{link.text}
</ExternalLink>
{/each}
</h3>
<div class="tags">
{#each tags as tag}
<Tag name={tag} />
{/each}
</div>
</div>
<style lang="postcss">
.card {
max-width: 30rem;
}
.tags {
display: flex;
flex-wrap: wrap;
justify-content: left;
align-items: center;
}
</style>

View file

@ -0,0 +1,47 @@
<script lang="ts">
import type { IconifyIcon } from "iconify-icon/dist/iconify-icon.js";
export let ariaLabel: string;
export let href: string;
export let clazz = "";
export let itemText: string;
export let icon: IconifyIcon;
</script>
<a
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
{href}
class={clazz}
>
<iconify-icon {icon} width="24" height="24" role="img" title={itemText} />
<p>{itemText}</p>
</a>
<style lang="postcss">
a {
display: grid;
justify-items: center;
font-weight: bold;
margin-right: 0;
text-decoration: none;
padding: 0.3rem;
margin-left: 1rem;
color: var(--lightGrey);
& p {
font-size: 1.5rem;
padding-top: 0.3rem;
margin: 0;
}
&:hover {
color: var(--shellYellow);
& p {
color: var(--shellYellow);
}
}
}
</style>

View file

@ -0,0 +1,88 @@
{
"courses": [
{
"name": "Wes Bos",
"externalLinks": [
{
"ariaLabel": "Wes Bos Courses",
"href": "https://wesbos.com/courses",
"showIcon": true,
"text": "Wes Bos"
}
],
"tags": ["React", "GraphQL", "Gatsby", "JavaScript"]
},
{
"name": "Scott Tolinski",
"externalLinks": [
{
"ariaLabel": "Scott Tolinski",
"href": "https://www.scotttolinski.com",
"showIcon": true,
"text": "Scott Tolinski"
},
{
"ariaLabel": "Levelup Tutorials",
"href": "https://levelup.video",
"showIcon": true,
"text": "Levelup Tutorials"
}
],
"tags": ["React", "TypeScript", "Svelte Kit", "Remix", "Figma", "Design Systems"]
},
{
"name": "Josh Comeau",
"externalLinks": [
{
"ariaLabel": "Josh Comeau",
"href": "https://www.joshwcomeau.com",
"showIcon": true,
"text": "Josh Comeau"
},
{
"ariaLabel": "The Joy of React",
"href": "https://www.joyofreact.com/",
"showIcon": true,
"text": "The Joy of React"
}
],
"tags": ["Full Stack React", "NextJS"]
},
{
"name": "Amy Kapernick",
"externalLinks": [
{
"ariaLabel": "Amy Kapernick",
"href": "https://www.amyskapers.dev/",
"showIcon": true,
"text": "Amy Kapernick"
}
],
"tags": ["Accessibility for Everyone"]
},
{
"name": "Andrew Mead",
"externalLinks": [
{
"ariaLabel": "Andrew Mead on Udemy",
"href": "https://www.udemy.com/user/andrewmead/",
"showIcon": true,
"text": "Andrew Mead"
}
],
"tags": ["GraphQL", "Apollo", "Prisma"]
},
{
"name": "Steven Grider",
"externalLinks": [
{
"ariaLabel": "Steven Grider on Udemy",
"href": "https://www.udemy.com/user/sgslo/",
"showIcon": true,
"text": "Steven Grider"
}
],
"tags": ["React", "Redux", "Docker", "GraphQL", "CSS", "HTML", "JavaScript"]
}
]
}

View file

@ -10,7 +10,7 @@ import {
USE_REDIS_CACHE USE_REDIS_CACHE
} from '$env/static/private'; } from '$env/static/private';
import intersect from 'just-intersect'; import intersect from 'just-intersect';
import type { Article, WallabagArticle } from '$lib/types/article'; import type { Article, ArticlePageLoad, WallabagArticle } from '$lib/types/article';
import { ArticleTag } from '$lib/types/articleTag'; import { ArticleTag } from '$lib/types/articleTag';
import type { PageQuery } from '$lib/types/pageQuery'; import type { PageQuery } from '$lib/types/pageQuery';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
@ -38,14 +38,12 @@ export async function fetchArticlesApi(
since: `${pageQuery.since}`, since: `${pageQuery.since}`,
page: `${pageQuery.page}` page: `${pageQuery.page}`
}); });
// console.log(`Entries params: ${entriesQueryParams}`);
if (USE_REDIS_CACHE) { if (USE_REDIS_CACHE) {
const cached = await redis.get(entriesQueryParams.toString()); const cached = await redis.get(entriesQueryParams.toString());
if (cached) { if (cached) {
const response = JSON.parse(cached); const response = JSON.parse(cached);
// console.log('Cache hit!');
const ttl = await redis.ttl(entriesQueryParams.toString()); const ttl = await redis.ttl(entriesQueryParams.toString());
return { ...response, cacheControl: `max-age=${ttl}` }; return { ...response, cacheControl: `max-age=${ttl}` };
@ -79,19 +77,12 @@ export async function fetchArticlesApi(
throw new Error(pageResponse.statusText); throw new Error(pageResponse.statusText);
} }
const cacheControl = pageResponse.headers.get('cache-control'); const cacheControl = pageResponse.headers.get('cache-control') || 'no-cache';
const { _embedded, page, pages, total, limit } = await pageResponse.json(); const { _embedded, page, pages, total, limit } = await pageResponse.json();
const articles: Article[] = []; const articles: Article[] = [];
// do {
// nbEntries += entries._embedded.items.length;
// console.log(`number of articles fetched: ${_embedded.items.length}`);
_embedded.items.forEach((article: WallabagArticle) => { _embedded.items.forEach((article: WallabagArticle) => {
// if (articles?.length === +WALLABAG_MAX_ARTICLES) {
// console.log('Reached 30 articles');
// 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);
@ -110,7 +101,7 @@ export async function fetchArticlesApi(
} }
}); });
const responseData = { const responseData: ArticlePageLoad = {
articles, articles,
currentPage: page, currentPage: page,
totalPages: pages > +WALLABAG_MAX_PAGES ? +WALLABAG_MAX_PAGES : pages, totalPages: pages > +WALLABAG_MAX_PAGES ? +WALLABAG_MAX_PAGES : pages,

View file

@ -1,14 +1,14 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import { WALLABAG_MAX_PAGES } from '$env/static/private'; import { WALLABAG_MAX_PAGES } from '$env/static/private';
import type { RequestHandler, RequestEvent } from './$types';
import { fetchArticlesApi } from '$root/routes/api'; import { fetchArticlesApi } from '$root/routes/api';
export const GET: RequestHandler = async ({ setHeaders, url }: RequestEvent) => { export async function GET({ setHeaders, url }) {
const page = url?.searchParams?.get('page') || '1';
if (+page > +WALLABAG_MAX_PAGES) {
error(404, 'Page does not exist');
}
try { try {
const page = url?.searchParams?.get('page') || '1';
if (+page > +WALLABAG_MAX_PAGES) {
throw new Error('Page does not exist');
}
const response = await fetchArticlesApi('get', `fetchArticles`, { const response = await fetchArticlesApi('get', `fetchArticles`, {
page, page,
limit: url?.searchParams?.get('limit') || '6' limit: url?.searchParams?.get('limit') || '6'
@ -27,11 +27,10 @@ export const GET: RequestHandler = async ({ setHeaders, url }: RequestEvent) =>
} }
} }
// console.log(`API response ${JSON.stringify(response)}`);
return json(response); return json(response);
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
throw error(404, 'Page does not exist'); error(404, 'Page does not exist');
} }
}; };

View file

@ -1,6 +1,5 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = async () => { export const load = async () => {
throw redirect(302, '/articles/1'); redirect(302, '/articles/1');
}; };

View file

@ -2,23 +2,15 @@ import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { WALLABAG_MAX_PAGES } from '$env/static/private'; 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 { Article } from '$lib/types/article'; import type { ArticlePageLoad } from '$lib/types/article';
import type { MetaTagsProps } from 'svelte-meta-tags';
export type ArticlePageLoad = {
articles: Article[];
currentPage: number;
totalPages: number;
limit: number;
totalArticles: number;
cacheControl: string;
};
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) { if (+page > +WALLABAG_MAX_PAGES) {
throw error(404, { error(404, {
message: 'Not found' 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 { articles, currentPage, totalPages, limit, totalArticles, cacheControl }: ArticlePageLoad =

View file

@ -1,16 +1,10 @@
import type { RequestHandler } from '@sveltejs/kit';
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import { html as toReactNode } from 'satori-html';
import FiraSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf';
import SocialImageCard from '$lib/components/socialImageCard.svelte'; import SocialImageCard from '$lib/components/socialImageCard.svelte';
import { dev } from '$app/environment';
import { componentToPng } from '$root/lib/renderImage'; import { componentToPng } from '$root/lib/renderImage';
const height = 630; const height = 630;
const width = 1200; const width = 1200;
export const GET: RequestHandler = async ({ url }) => { export async function GET({ url }) {
try { try {
const ogImage = `${new URL(url.origin).href}/b_shell_nut_favicon.png`; const ogImage = `${new URL(url.origin).href}/b_shell_nut_favicon.png`;
const header = url.searchParams.get('header') ?? undefined; const header = url.searchParams.get('header') ?? undefined;
@ -22,11 +16,11 @@ export const GET: RequestHandler = async ({ url }) => {
page, page,
content, content,
image: ogImage, image: ogImage,
width, width: `${width}`,
height, height: `${height}`,
url: new URL(url.origin).href url: new URL(url.origin).href
}, height, width); }, height, width);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}; }

View file

@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-vercel'; import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/kit/vite'; 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';
import mdsvexConfig from './mdsvex.config.js'; import mdsvexConfig from './mdsvex.config.js';