mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
commit
4f7ecab394
27 changed files with 4874 additions and 3851 deletions
|
|
@ -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=''
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/** @type { import("eslint").Linter.Config } */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: [
|
extends: [
|
||||||
|
|
|
||||||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
||||||
v18
|
v22
|
||||||
|
|
@ -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": ["."],
|
||||||
|
|
|
||||||
95
package.json
95
package.json
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8225
pnpm-lock.yaml
8225
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
109
src/lib/components/Pagination.svelte
Normal file
109
src/lib/components/Pagination.svelte
Normal 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>
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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}`}
|
|
||||||
>
|
|
||||||
← <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> →
|
|
||||||
</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>
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
7
src/state/search.ts
Normal 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', []);
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue