mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
commit
dc983df55c
26 changed files with 766 additions and 816 deletions
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -2,6 +2,7 @@
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Bandcamp",
|
"Bandcamp",
|
||||||
"bradleyshellnut",
|
"bradleyshellnut",
|
||||||
|
"clazz",
|
||||||
"iconify",
|
"iconify",
|
||||||
"Mullvad",
|
"Mullvad",
|
||||||
"nextjs",
|
"nextjs",
|
||||||
|
|
|
||||||
16
package.json
16
package.json
|
|
@ -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": {
|
||||||
|
|
|
||||||
928
pnpm-lock.yaml
928
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
BIN
src/lib/assets/images/cottage.png
Normal file
BIN
src/lib/assets/images/cottage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
src/lib/assets/images/rural.png
Normal file
BIN
src/lib/assets/images/rural.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
|
|
@ -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`} />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
12
src/lib/types/courses.ts
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -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[];
|
||||||
|
|
|
||||||
|
|
@ -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…</h2>
|
<h2>Other fun things about me…</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));
|
||||||
|
|
|
||||||
40
src/routes/about/CourseCard.svelte
Normal file
40
src/routes/about/CourseCard.svelte
Normal 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>
|
||||||
47
src/routes/about/TechListItem.svelte
Normal file
47
src/routes/about/TechListItem.svelte
Normal 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>
|
||||||
88
src/routes/about/course.json
Normal file
88
src/routes/about/course.json
Normal 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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue