mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
Fetching 500 Wallabag articles, since we cannot filter tags easily, then filter tags in the API.
This commit is contained in:
parent
139fea9490
commit
e3f2cae296
12 changed files with 133 additions and 19 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -10,4 +10,5 @@ vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
.fleet
|
.fleet
|
||||||
.idea
|
.idea
|
||||||
|
.vercel
|
||||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
|
|
@ -1,3 +1,12 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": ["bradleyshellnut", "iconify", "Mullvad", "Obispo", "Syncthing"]
|
"cSpell.words": [
|
||||||
|
"bradleyshellnut",
|
||||||
|
"iconify",
|
||||||
|
"Mullvad",
|
||||||
|
"nextjs",
|
||||||
|
"Obispo",
|
||||||
|
"selfhosting",
|
||||||
|
"Syncthing",
|
||||||
|
"Wallabag"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"iconify-icon": "^1.0.3",
|
"iconify-icon": "^1.0.3",
|
||||||
|
"just-intersect": "^4.3.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ specifiers:
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
eslint-plugin-svelte3: ^4.0.0
|
eslint-plugin-svelte3: ^4.0.0
|
||||||
iconify-icon: ^1.0.3
|
iconify-icon: ^1.0.3
|
||||||
|
just-intersect: ^4.3.0
|
||||||
postcss: ^8.4.21
|
postcss: ^8.4.21
|
||||||
postcss-import: ^15.1.0
|
postcss-import: ^15.1.0
|
||||||
postcss-load-config: ^4.0.1
|
postcss-load-config: ^4.0.1
|
||||||
|
|
@ -57,6 +58,7 @@ devDependencies:
|
||||||
eslint-config-prettier: 8.6.0_eslint@8.32.0
|
eslint-config-prettier: 8.6.0_eslint@8.32.0
|
||||||
eslint-plugin-svelte3: 4.0.0_tmo5zkisvhu6htudosk5k7m6pu
|
eslint-plugin-svelte3: 4.0.0_tmo5zkisvhu6htudosk5k7m6pu
|
||||||
iconify-icon: 1.0.3
|
iconify-icon: 1.0.3
|
||||||
|
just-intersect: 4.3.0
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
postcss-import: 15.1.0_postcss@8.4.21
|
postcss-import: 15.1.0_postcss@8.4.21
|
||||||
postcss-load-config: 4.0.1_postcss@8.4.21
|
postcss-load-config: 4.0.1_postcss@8.4.21
|
||||||
|
|
@ -2369,6 +2371,10 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/just-intersect/4.3.0:
|
||||||
|
resolution: {integrity: sha512-XdAiJUmfM7VOoBUh7I4xo+0YYuzCPiV0Xf21oAmR+1j6a/P8KxJUCigZDblmURZmkIWffNziY2sbSaHrJbNcuA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/kleur/4.1.5:
|
/kleur/4.1.5:
|
||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
|
||||||
41
src/lib/stores/articleStore.ts
Normal file
41
src/lib/stores/articleStore.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import type { Article } from '$lib/types/article';
|
||||||
|
|
||||||
|
// Custom store
|
||||||
|
const state = () => {
|
||||||
|
const { subscribe, set, update } = writable<Article[]>([]);
|
||||||
|
|
||||||
|
function addAll(articles: Article[]) {
|
||||||
|
for (const article of articles) {
|
||||||
|
add(article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(article: Article) {
|
||||||
|
update((store) => [...store, article]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSorted(article: Article, index: number) {
|
||||||
|
update((store) => {
|
||||||
|
store.splice(index, 0, article);
|
||||||
|
return store;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(hashed_url: string) {
|
||||||
|
update((store) => {
|
||||||
|
const newStore = store.filter((item: Article) => item.hashed_url !== hashed_url);
|
||||||
|
return [...newStore];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAll() {
|
||||||
|
update(() => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { subscribe, set, update, add, addSorted, addAll, remove, removeAll };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const articleStore = state();
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
export type Article {
|
import type { ArticleTag } from './articleTag';
|
||||||
tags: string[];
|
|
||||||
|
export type Article = {
|
||||||
|
tags: ArticleTag[];
|
||||||
title: string;
|
title: string;
|
||||||
url: URL;
|
url: URL;
|
||||||
hashed_url: string;
|
hashed_url: string;
|
||||||
|
|
@ -8,4 +10,22 @@ export type Article {
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
archived_at: Date | null;
|
archived_at: Date | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type WallabagArticle = {
|
||||||
|
tags: WallabagTag[];
|
||||||
|
title: string;
|
||||||
|
url: URL;
|
||||||
|
hashed_url: string;
|
||||||
|
reading_time: number;
|
||||||
|
preview_picture: string;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
archived_at: Date | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WallabagTag = {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
17
src/lib/types/articleTag.ts
Normal file
17
src/lib/types/articleTag.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export enum ArticleTag {
|
||||||
|
TECH = 'tech',
|
||||||
|
TECHNOLOGY = 'technology',
|
||||||
|
SELFHOSTING = 'selfhosting',
|
||||||
|
PROGRAMMING = 'programming',
|
||||||
|
PRIVACY = 'privacy',
|
||||||
|
DEVELOPMENT = 'development',
|
||||||
|
VITE = 'vite',
|
||||||
|
SVELTE = 'svelte',
|
||||||
|
REACT = 'react',
|
||||||
|
JAVASCRIPT = 'javascript',
|
||||||
|
GATSBY = 'gatsby',
|
||||||
|
SVELTEKIT = 'sveltekit',
|
||||||
|
NEXTJS = 'nextjs',
|
||||||
|
CSS = 'css',
|
||||||
|
HTML = 'html'
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { error, type ServerLoad } from '@sveltejs/kit';
|
export const prerender = true;
|
||||||
import { fetchArticlesApi } from './api';
|
import { type ServerLoad } from '@sveltejs/kit';
|
||||||
// import type { PageServerLoad } from './$types';
|
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ isDataRequest, cookies, params, setHeaders }) => {
|
export const load: ServerLoad = async ({ isDataRequest, cookies, params, setHeaders }) => {
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import {
|
||||||
WALLABAG_PASSWORD,
|
WALLABAG_PASSWORD,
|
||||||
WALLABAG_URL
|
WALLABAG_URL
|
||||||
} from '$env/static/private';
|
} from '$env/static/private';
|
||||||
import type { Article } from '$root/lib/types/article';
|
import intersect from 'just-intersect';
|
||||||
|
import type { Article, WallabagArticle } from '$root/lib/types/article';
|
||||||
|
import { ArticleTag } from '$root/lib/types/articleTag';
|
||||||
import type { PageQuery } from '$root/lib/types/pageQuery';
|
import type { PageQuery } from '$root/lib/types/pageQuery';
|
||||||
import { URLSearchParams } from 'url';
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
|
@ -37,7 +39,7 @@ export async function fetchArticlesApi(
|
||||||
|
|
||||||
const pageQuery: PageQuery = {
|
const pageQuery: PageQuery = {
|
||||||
sort: 'updated',
|
sort: 'updated',
|
||||||
perPage: 6,
|
perPage: 500,
|
||||||
since: 0
|
since: 0
|
||||||
};
|
};
|
||||||
const entriesQueryParams = new URLSearchParams(pageQuery);
|
const entriesQueryParams = new URLSearchParams(pageQuery);
|
||||||
|
|
@ -67,11 +69,27 @@ export async function fetchArticlesApi(
|
||||||
// do {
|
// do {
|
||||||
// nbEntries += entries._embedded.items.length;
|
// nbEntries += entries._embedded.items.length;
|
||||||
console.log(`number of articles fetched: ${entries._embedded.items.length}`);
|
console.log(`number of articles fetched: ${entries._embedded.items.length}`);
|
||||||
entries._embedded.items.forEach((article: Article) => {
|
entries._embedded.items.forEach((article: WallabagArticle) => {
|
||||||
article.created_at = new Date(article.created_at);
|
if (articles?.length === 30) {
|
||||||
article.updated_at = new Date(article.updated_at);
|
console.log('Reached 30 articles');
|
||||||
article.archived_at = article.archived_at ? new Date(article.archived_at) : null;
|
return;
|
||||||
articles.push(article);
|
}
|
||||||
|
const rawTags = article?.tags?.map((tag) => tag.slug);
|
||||||
|
if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) {
|
||||||
|
const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag);
|
||||||
|
|
||||||
|
articles.push({
|
||||||
|
tags,
|
||||||
|
title: article.title,
|
||||||
|
url: article.url,
|
||||||
|
hashed_url: article.hashed_url,
|
||||||
|
reading_time: article.reading_time,
|
||||||
|
preview_picture: article.preview_picture,
|
||||||
|
created_at: new Date(article.created_at),
|
||||||
|
updated_at: new Date(article.updated_at),
|
||||||
|
archived_at: article.archived_at ? new Date(article.archived_at) : null
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (!entries._links.next) {
|
// if (!entries._links.next) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
|
import { Article } from "$lib/types/article";
|
||||||
|
import { ArticleTag } from "$lib/types/articleTag";
|
||||||
import SEO from "$root/lib/components/SEO.svelte";
|
import SEO from "$root/lib/components/SEO.svelte";
|
||||||
|
|
||||||
$: ({ articles } = $page.data);
|
$: ({ articles } = $page.data);
|
||||||
|
|
@ -24,7 +26,7 @@
|
||||||
base="/articles"
|
base="/articles"
|
||||||
/> -->
|
/> -->
|
||||||
<div class="articlesStyles">
|
<div class="articlesStyles">
|
||||||
{#each articles as article}
|
{#each articles as article (article.hashed_url)}
|
||||||
<div class="articleStyles card">
|
<div class="articleStyles card">
|
||||||
<section>
|
<section>
|
||||||
<h3>
|
<h3>
|
||||||
|
|
@ -43,7 +45,7 @@
|
||||||
<div class="tagStyles">
|
<div class="tagStyles">
|
||||||
<p>Tags:</p>
|
<p>Tags:</p>
|
||||||
{#each article.tags as tag}
|
{#each article.tags as tag}
|
||||||
<p>{tag.label}</p>
|
<p>{tag}</p>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export const load: PageLoad = async ({ fetch, parent, url, setHeaders }) => {
|
||||||
const parentData = await parent();
|
const parentData = await parent();
|
||||||
|
|
||||||
const cacheBust = getCurrentCookieValue('articles-cache') || parentData.articlesCacheBust;
|
const cacheBust = getCurrentCookieValue('articles-cache') || parentData.articlesCacheBust;
|
||||||
const search = url.searchParams.get('search') || '';
|
// const search = url.searchParams.get('search') || '';
|
||||||
|
|
||||||
const resp = await fetch(`/api/articles?cache=${cacheBust}`);
|
const resp = await fetch(`/api/articles?cache=${cacheBust}`);
|
||||||
const articles: Article[] = await resp.json();
|
const articles: Article[] = await resp.json();
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Bee, Shell, Nut, and Adventure Icons made by{' '}
|
Bee, Shell, Nut, and Seattle Icons made by{' '}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://www.flaticon.com/authors/freepik"
|
href="https://www.flaticon.com/authors/freepik"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue