Testing out pagination articles and fetching on each slug page.

This commit is contained in:
Bradley Shellnut 2023-02-09 16:25:21 -08:00
parent e3f2cae296
commit 1c1ccbbf01
20 changed files with 433 additions and 396 deletions

View file

@ -22,6 +22,7 @@
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
"@rgossiaux/svelte-headlessui": "^1.0.2", "@rgossiaux/svelte-headlessui": "^1.0.2",
"@sveltejs/adapter-auto": "^1.0.0", "@sveltejs/adapter-auto": "^1.0.0",
"@sveltejs/adapter-static": "^2.0.0",
"@sveltejs/adapter-vercel": "^1.0.5", "@sveltejs/adapter-vercel": "^1.0.5",
"@sveltejs/kit": "^1.0.0", "@sveltejs/kit": "^1.0.0",
"@types/postcss-preset-env": "^8.0.0", "@types/postcss-preset-env": "^8.0.0",

View file

@ -9,6 +9,7 @@ specifiers:
'@playwright/test': ^1.28.1 '@playwright/test': ^1.28.1
'@rgossiaux/svelte-headlessui': ^1.0.2 '@rgossiaux/svelte-headlessui': ^1.0.2
'@sveltejs/adapter-auto': ^1.0.0 '@sveltejs/adapter-auto': ^1.0.0
'@sveltejs/adapter-static': ^2.0.0
'@sveltejs/adapter-vercel': ^1.0.5 '@sveltejs/adapter-vercel': ^1.0.5
'@sveltejs/kit': ^1.0.0 '@sveltejs/kit': ^1.0.0
'@types/postcss-preset-env': ^8.0.0 '@types/postcss-preset-env': ^8.0.0
@ -48,6 +49,7 @@ devDependencies:
'@playwright/test': 1.29.2 '@playwright/test': 1.29.2
'@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.55.1 '@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.55.1
'@sveltejs/adapter-auto': 1.0.2_@sveltejs+kit@1.2.2 '@sveltejs/adapter-auto': 1.0.2_@sveltejs+kit@1.2.2
'@sveltejs/adapter-static': 2.0.0_@sveltejs+kit@1.2.2
'@sveltejs/adapter-vercel': 1.0.5_@sveltejs+kit@1.2.2 '@sveltejs/adapter-vercel': 1.0.5_@sveltejs+kit@1.2.2
'@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4 '@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4
'@types/postcss-preset-env': 8.0.0_postcss@8.4.21 '@types/postcss-preset-env': 8.0.0_postcss@8.4.21
@ -696,6 +698,14 @@ packages:
import-meta-resolve: 2.2.1 import-meta-resolve: 2.2.1
dev: true dev: true
/@sveltejs/adapter-static/2.0.0_@sveltejs+kit@1.2.2:
resolution: {integrity: sha512-M9F8EyCIdVcpZu0zvS7U7v+rc7elNNDUkMHLsOkJYlfQ9rDdif3fqteMqddcrq1lFPEGH1ErdTjpsXdb3k1e8w==}
peerDependencies:
'@sveltejs/kit': ^1.5.0
dependencies:
'@sveltejs/kit': 1.2.2_svelte@3.55.1+vite@4.0.4
dev: true
/@sveltejs/adapter-vercel/1.0.5_@sveltejs+kit@1.2.2: /@sveltejs/adapter-vercel/1.0.5_@sveltejs+kit@1.2.2:
resolution: {integrity: sha512-TnFw+2ZwUuzguIeh013h0eiFmHylBuL1aV3jcUWbJmBLRo6YA0Ti8X8VXgFP4izP7XGuLKdpUIMg7HaZe21LbA==} resolution: {integrity: sha512-TnFw+2ZwUuzguIeh013h0eiFmHylBuL1aV3jcUWbJmBLRo6YA0Ti8X8VXgFP4izP7XGuLKdpUIMg7HaZe21LbA==}
peerDependencies: peerDependencies:

View file

@ -0,0 +1,85 @@
<script lang="ts">
export let additionalClasses: string;
export let pageSize: number;
export let totalCount: number;
export let currentPage: number;
export let skip: number;
export let base: string;
// make some variables
console.log(`Total count: ${totalCount}`);
console.log(`Page Size: ${pageSize}`);
const totalPages = Math.ceil(totalCount / pageSize);
console.log(`Total Pages: ${totalPages}`)
console.log(`Current Page: ${currentPage} and type: ${typeof currentPage}`)
let prevPage = currentPage - 1;
console.log(`PrevPage: ${prevPage}`)
console.log(`Pagination current Page: ${currentPage}`)
let nextPage = currentPage + 1;
let hasNextPage = nextPage <= totalPages;
let hasPrevPage = prevPage >= 1;
console.log({ nextPage, hasNextPage, hasPrevPage })
</script>
<div class={`paginationStyles ${additionalClasses}`}>
<a
title={`${!hasPrevPage ? 'No ' : ''}Prev Page`}
aria-disabled={!hasPrevPage}
href={`${base}/${prevPage}`}
>
&#8592; <span class="word">Prev</span>
</a>
{#each { length: totalPages } as _, i}
<a
aria-current={currentPage === i + 1}
href={`${base}/${i + 1}`}
>
{i + 1}
</a>
{/each}
<a
title={`${!hasNextPage ? 'No ' : ''}Next Page`}
aria-disabled={!hasNextPage}
href={`${base}/${nextPage}`}
>
<span class="word">Next</span> &#8594;
</a>
</div>
<style lang="postcss">
a {
&[aria-current="true"] {
color: var(--shellYellow)
}
&[aria-disabled="true"] {
pointer-events: none;
color: var(--lightGrey);
text-decoration: line-through;
}
}
.paginationStyles {
display: flex;
align-content: center;
align-items: center;
justify-items: center;
border: 1px solid var(--grey);
margin: 3rem 0;
border-radius: 5px;
text-align: center;
font-size: 1.5rem;
& > * {
padding: 1rem;
flex: 1;
border-right: 1px solid var(--grey);
text-decoration: none;
}
@media (max-width: 800px) {
.word {
display: none;
}
}
}
</style>

View file

@ -6,6 +6,7 @@ const state = () => {
const { subscribe, set, update } = writable<Article[]>([]); const { subscribe, set, update } = writable<Article[]>([]);
function addAll(articles: Article[]) { function addAll(articles: Article[]) {
// console.log(typeof articles);
for (const article of articles) { for (const article of articles) {
add(article); add(article);
} }

View file

@ -1,5 +1,8 @@
export type PageQuery { export type PageQuery = {
sort: string; sort: string;
perPage: number; perPage: number;
since: number; since: number;
} page: number;
tags: string;
content: 'metadata' | 'full';
};

View file

@ -1,24 +0,0 @@
export const prerender = true;
import { type ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ isDataRequest, cookies, params, setHeaders }) => {
const queryParams = {
// ids: `${params?.id}`,
// fields:
// 'id,name,price,min_age,min_players,max_players,thumb_url,playtime,min_playtime,max_playtime,min_age,description,year_published,url,image_url'
};
const initialRequest = !isDataRequest;
console.log(`Is initialRequest: ${initialRequest}`);
const cacheValue = `${initialRequest ? +new Date() : cookies.get('articles-cache')}`;
console.log(`Cache Value: ${cacheValue}`);
if (initialRequest) {
cookies.set('articles-cache', cacheValue, { path: '/', httpOnly: false });
}
return {
articlesCacheBust: cacheValue
};
};

View file

@ -1,101 +0,0 @@
<script lang="ts">
import 'iconify-icon';
import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer/index.svelte';
import '$root/styles/styles.pcss';
import Analytics from '$lib/components/analytics/index.svelte';
import SEO from '$lib/components/SEO.svelte';
const dev = process.env.NODE_ENV !== 'production';
</script>
{#if !dev}
<Analytics />
{/if}
<SEO />
<div class="wrapper">
<Header />
<main>
<slot />
</main>
<Footer />
</div>
<style lang="postcss">
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
h3 {
color: white;
}
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
main {
flex: 1;
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 2rem 0rem;
max-width: 85vw;
@media (min-width: 1600px) {
max-width: 70vw;
}
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
:global(p) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
:global(li) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
</style>

3
src/routes/+layout.ts Normal file
View file

@ -0,0 +1,3 @@
<script lang="ts">export const prerender = true;</script>;
export const load: PageLoad = async () => {};

View file

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from "./$types";
import SEO from '$root/lib/components/SEO.svelte'; import SEO from '$root/lib/components/SEO.svelte';
export let data: PageData; // export let data: PageData;
const userNames = { const userNames = {
github: 'BradNut', github: 'BradNut',
linkedIn: 'bradley-shellnut', linkedIn: 'bradley-shellnut',

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -3,7 +3,9 @@ import {
WALLABAG_CLIENT_SECRET, WALLABAG_CLIENT_SECRET,
WALLABAG_USERNAME, WALLABAG_USERNAME,
WALLABAG_PASSWORD, WALLABAG_PASSWORD,
WALLABAG_URL WALLABAG_URL,
WALLABAG_MAX_ARTICLES,
PAGE_SIZE
} from '$env/static/private'; } from '$env/static/private';
import intersect from 'just-intersect'; import intersect from 'just-intersect';
import type { Article, WallabagArticle } from '$root/lib/types/article'; import type { Article, WallabagArticle } from '$root/lib/types/article';
@ -39,8 +41,11 @@ export async function fetchArticlesApi(
const pageQuery: PageQuery = { const pageQuery: PageQuery = {
sort: 'updated', sort: 'updated',
perPage: 500, perPage: +PAGE_SIZE,
since: 0 since: 0,
page: +queryParams?.page || 1,
tags: 'programming',
content: 'metadata'
}; };
const entriesQueryParams = new URLSearchParams(pageQuery); const entriesQueryParams = new URLSearchParams(pageQuery);
console.log(`Entries params: ${entriesQueryParams}`); console.log(`Entries params: ${entriesQueryParams}`);
@ -63,17 +68,17 @@ export async function fetchArticlesApi(
throw new Error(pageResponse.statusText); throw new Error(pageResponse.statusText);
} }
const entries = await pageResponse.json(); const { _embedded, page, pages, total, limit } = await pageResponse.json();
const articles: Article[] = []; const articles: Article[] = [];
// 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: ${_embedded.items.length}`);
entries._embedded.items.forEach((article: WallabagArticle) => { _embedded.items.forEach((article: WallabagArticle) => {
if (articles?.length === 30) { // if (articles?.length === +WALLABAG_MAX_ARTICLES) {
console.log('Reached 30 articles'); // console.log('Reached 30 articles');
return; // 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);
@ -105,5 +110,5 @@ export async function fetchArticlesApi(
// entries = await response.json(); // entries = await response.json();
// } while (entries._links.next); // } while (entries._links.next);
return { articles }; return { articles, currentPage: page, totalPages: pages, limit, totalArticles: total };
} }

View file

@ -2,25 +2,27 @@ import { json } from '@sveltejs/kit';
import type { RequestHandler, RequestEvent } from './$types'; import type { RequestHandler, RequestEvent } from './$types';
import { fetchArticlesApi } from '$root/routes/api'; import { fetchArticlesApi } from '$root/routes/api';
export const GET: RequestHandler = async ({ url, setHeaders, request, params }: RequestEvent) => { export const GET: RequestHandler = async ({ url, setHeaders }: RequestEvent) => {
try { try {
const response = await fetchArticlesApi('get', `search`, {}); const response = await fetchArticlesApi('get', `fetchArticles`, {
page: url?.searchParams?.get('page') || '1'
});
if (response?.articles) { if (response?.articles) {
setHeaders({ setHeaders({
'cache-control': 'max-age=604800' 'cache-control': 'max-age=60'
}); });
const articlesResponse = response.articles; // const articlesResponse = response.articles;
console.log(`Found articles ${articlesResponse.length}`); // console.log(`Found articles ${articlesResponse?.articles?.length}`);
const articles = []; // const articles = [];
for (const article of articlesResponse) { // for (const article of articlesResponse) {
const { tags, title, url, hashed_url, reading_time, preview_picture } = article; // const { tags, title, url, hashed_url, reading_time, preview_picture } = article;
articles.push({ tags, title, url, hashed_url, reading_time, preview_picture }); // articles.push({ tags, title, url, hashed_url, reading_time, preview_picture });
} // }
return json(articles); return json(response);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View file

@ -0,0 +1,14 @@
<script lang="ts">
import { page } from "$app/stores";
console.log(`error page ${JSON.stringify($page)}`)
</script>
{#if $page.status === 404}
<h1>Sorry page not found! 🤷🏼</h1>
<p>You just hit a route that doesn't exist.</p>
{/if}
{#if $page.status === 500}
<h1>Sorry an unexpected error occurred! 😿</h1>
<p>Please try again later. </p>
{/if}

View file

@ -1,17 +0,0 @@
import { getCurrentCookieValue } from '$lib/util/cookieUtils';
import type { Article } from '$root/lib/types/article';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, parent, url, setHeaders }) => {
const parentData = await parent();
const cacheBust = getCurrentCookieValue('articles-cache') || parentData.articlesCacheBust;
// const search = url.searchParams.get('search') || '';
const resp = await fetch(`/api/articles?cache=${cacheBust}`);
const articles: Article[] = await resp.json();
return {
articles
};
};

View file

@ -1,30 +1,43 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/stores"; import { page } from "$app/stores";
import { Article } from "$lib/types/article"; import { articleStore } from "$lib/stores/articleStore";
import { ArticleTag } from "$lib/types/articleTag"; import { ArticleTag } from "$lib/types/articleTag";
import Pagination from "$lib/components/pagination/index.svelte";
import SEO from "$root/lib/components/SEO.svelte"; import SEO from "$root/lib/components/SEO.svelte";
import type { Article } from "$root/lib/types/article";
import type { PageData } from "./$types";
$: ({ articles } = $page.data); export let data: PageData;
let articles: Article[];
const currentPage: number = 1; $: ({ articles, currentPage, totalPages, totalArticles, limit } = data);
// const articles = data?.articles?.nodes; // let currentPage: number;
// const maxArticles = parseInt(process.env.GATSBY_WALLABAG_MAX_ARTICLES); // let perPage: number;
// const totalCount = // let maxPages: number;
// data.articles.totalCount > maxArticles ? maxArticles : articles.length; // $: ({ currentPage, perPage, maxPages } = $page?.data);
// console.log('Articles Page params', $page?.params);
// console.log('Articles Page', { currentPage, perPage, maxPages });
// console.log(`Page data: ${JSON.stringify(data)}`)
// console.log(`Article Page Path Slug ${$page.params.page}`);
// console.log(`Article Page Current Page: ${currentPage}`)
// $: start = (currentPage - 1) * perPage;
// $: skip = currentPage * perPage;
// console.log(`Article Store size: ${$articleStore.length}`);
// $: articles = $articleStore.slice(start, start + perPage);
</script> </script>
<SEO title={`Tech Articles - Page ${currentPage}`} /> <SEO title={`Tech Articles - Page ${$page?.params?.page}`} />
<div class="pageStyles"> <div class="pageStyles">
<h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1> <h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1>
<!-- <Pagination <Pagination
clazz="top-pagination" additionalClasses="top-pagination"
pageSize={parseInt(process.env.GATSBY_PAGE_SIZE)} pageSize={limit}
totalCount={totalCount} totalCount={totalArticles}
currentPage={pageContext.currentPage || 1} currentPage={currentPage || 1}
skip={pageContext.skip} skip={page}
base="/articles" base="/articles"
/> --> />
<div class="articlesStyles"> <div class="articlesStyles">
{#each articles as article (article.hashed_url)} {#each articles as article (article.hashed_url)}
<div class="articleStyles card"> <div class="articleStyles card">
@ -33,7 +46,7 @@
<a <a
target="_blank" target="_blank"
aria-label={`Link to ${article.title}`} aria-label={`Link to ${article.title}`}
href={article.url} href={article.url.href}
rel="noreferrer" rel="noreferrer"
> >
{article.title} {article.title}
@ -52,14 +65,14 @@
</div> </div>
{/each} {/each}
</div> </div>
<!-- <Pagination <Pagination
clazz="bottom-pagination" additionalClasses="bottom-pagination"
pageSize={parseInt(process.env.GATSBY_PAGE_SIZE)} pageSize={limit}
totalCount={totalCount} totalCount={totalPages}
currentPage={pageContext.currentPage || 1} currentPage={currentPage || 1}
skip={pageContext.skip} skip={page}
base="/articles" base="/articles"
/> --> />
</div> </div>
<style lang="postcss"> <style lang="postcss">
@ -95,6 +108,9 @@
display: grid; display: grid;
grid-template-rows: 1fr auto; grid-template-rows: 1fr auto;
align-items: start; align-items: start;
a {
cursor: pointer;
}
/* p { /* p {
margin: 0.4rem 0.25rem; margin: 0.4rem 0.25rem;

View file

@ -0,0 +1,43 @@
import type { PageLoad } from './$types';
import type { Article } from '$root/lib/types/article';
export const load: PageLoad = async ({ fetch, params }) => {
const { page } = params;
const resp = await fetch(`/api/articles?page=${page}`);
const {
articles,
currentPage,
totalPages,
limit,
totalArticles
}: {
articles: Article[];
currentPage: number;
totalPages: number;
limit: number;
totalArticles: number;
} = await resp.json();
return {
articles,
currentPage,
totalPages,
limit,
totalArticles
};
// console.log(`Page: ${page}`);
// try {
// if (page && +page <= 5 && +page > 0) {
// return {
// currentPage: +page,
// perPage: parentData?.perPage,
// maxPages: parentData?.totalPages
// };
// } else {
// console.log('Page load error 404');
// throw error(404, 'Not found');
// }
// } catch (e) {
// console.error(e);
// throw error(500, 'Error');
// }
};

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -1 +0,0 @@
export const prerender = true;

View file

@ -5,216 +5,217 @@
<SEO title="Privacy Blog" /> <SEO title="Privacy Blog" />
<div> <div>
<div> <h1>Privacy</h1>
<h1>Privacy</h1> <p>
<p> Long story short, I believe everyone should know who has your personal
Long story short, I believe everyone should know who has your personal data, how it is being collected/stored, and what it is being used for.
data, how it is being collected/stored, and what it is being used for. </p>
</p> <p>
<p> However, it is ultimately up to each person to determine how much data
However, it is ultimately up to each person to determine how much data they are willing to give to any business/entity.
they are willing to give to any business/entity. </p>
</p> <p>
<p> For the sake of transparency I am using{' '}
For the sake of transparency I am using{' '} <a
target="_blank"
aria-label="Umami Analytics FAQ"
href="https://umami.is/docs/faq"
rel="noreferrer"
>
Umami Analytics
</a>{' '}
to anonymously track visits to my site. You can completely block this
if you want by either using an AdBlocker like{' '}
<a
target="_blank"
aria-label="uBlock Origin"
href="https://ublockorigin.com/"
rel="noreferrer"
>
uBlock Origin
</a>{' '}
and/or set your browser to send "Do Not Track" requests as I honor
them.
</p>
</div>
<div style="margin-top: 2.5rem;">
<h2>Useful Resources</h2>
<p>
Here are a few sites/lists of privacy oriented software for you to
check out:
</p>
<ul>
<li>
<a <a
target="_blank" target="_blank"
aria-label="Umami Analytics FAQ" aria-label="Privacy Respecting Software"
href="https://umami.is/docs/faq" href="https://github.com/Lissy93/personal-security-checklist/blob/master/5_Privacy_Respecting_Software.md"
rel="noreferrer" rel="noreferrer"
> >
Umami Analytics Privacy Respecting Software
</a>{' '} </a>
to anonymously track visits to my site. You can completely block this </li>
if you want by either using an AdBlocker like{' '} <li>
<a <a
target="_blank" target="_blank"
aria-label="uBlock Origin" aria-label="Privacy Guides"
href="https://ublockorigin.com/" href="https://www.privacyguides.org/"
rel="noreferrer" rel="noreferrer"
> >
uBlock Origin Privacy Guides
</a>
</li>
<li>
<a
target="_blank"
aria-label="Ethical Alternatives"
href="https://ethical.net/resources/"
rel="noreferrer"
>
Ethical Alternatives
</a>
</li>
</ul>
</div>
<div>
<h3>Privacy Centric Paid Services I use:</h3>
<ul>
<li>
Article Saving:{' '}
<a
target="_blank"
aria-label="Wallabag Article Saver"
href="https://github.com/wallabag/wallabag"
rel="noreferrer"
>
Wallabag
</a>
</li>
<li>
Anonymous Email Forwarding:{' '}
<a
target="_blank"
aria-label="AnonAddy Email Forwarding"
href="https://anonaddy.com/"
rel="noreferrer"
>
AnonAddy
</a>
</li>
<li>
Email:{' '}
<a
target="_blank"
aria-label="ProtonMail"
href="https://protonmail.com"
rel="noreferrer"
>
ProtonMail
</a>
</li>
<li>
Notes:{' '}
<a
target="_blank"
aria-label="Standard Notes"
href="https://standardnotes.org/"
rel="noreferrer"
>
Standard Notes
</a>
</li>
<li>
VPN:{' '}
<a
target="_blank"
aria-label="Mullvad VPN"
href="https://mullvad.net/en/"
rel="noreferrer"
>
Mullvad VPN
</a>
</li>
</ul>
</div>
<div>
<h3>NAS Servers for Self Hosting:</h3>
<ul>
<li>
<a
target="_blank"
aria-label="Synology NAS"
href="https://synology.com/"
rel="noreferrer"
>
Synology
</a>
: An easy, not cheap, local solution for Google Services like Drive,
Drive, Photos, Calendar, other services using Docker, etc. (Yes I
should use{' '}
<a
target="_blank"
aria-label="NextCloud Local Hosting Service"
href="https://nextcloud.com/"
rel="noreferrer"
>
NextCloud
</a>
...maybe eventually)
</li>
<li>
Mac Mini: Used as a{' '}
<a
target="_blank"
aria-label="Plex"
href="https://www.plex.tv/"
rel="noreferrer"
>
Plex Server
</a>{' '} </a>{' '}
and/or set your browser to send "Do Not Track" requests as I honor for my Movies/TV/Music until I build a dedicated NAS and maybe
them. switch to{' '}
</p> <a
</div> target="_blank"
<div style="margin-top: 2.5rem;"> aria-label="JellyFin"
<h2>Useful Resources</h2> href="https://jellyfin.org/"
<p> rel="noreferrer"
Here are a few sites/lists of privacy oriented software for you to >
check out: JellyFin
</p> </a>{' '}
<ul> once it is more mature.
<li> </li>
<a </ul>
target="_blank" </div>
aria-label="Privacy Respecting Software" <div>
href="https://github.com/Lissy93/personal-security-checklist/blob/master/5_Privacy_Respecting_Software.md" <h3>Software Deployed:</h3>
rel="noreferrer" <ul>
> <li>
Privacy Respecting Software <a
</a> target="_blank"
</li> aria-label="Traefik Cloud Proxy"
<li> href="https://github.com/traefik/traefik"
<a rel="noreferrer"
target="_blank" >
aria-label="Privacy Guides" Traefik
href="https://www.privacyguides.org/" </a>
rel="noreferrer" : A HTTP reverse proxy and load balancer.
> </li>
Privacy Guides <li>
</a> <a
</li> target="_blank"
<li> aria-label="Syncthing File Synchronization"
<a href="https://github.com/syncthing/syncthing"
target="_blank" rel="noreferrer"
aria-label="Ethical Alternatives" >
href="https://ethical.net/resources/" Syncthing
rel="noreferrer" </a>
> : An open source file synchronization program.
Ethical Alternatives </li>
</a> </ul>
</li>
</ul>
</div>
<div>
<h3>Privacy Centric Paid Services I use:</h3>
<ul>
<li>
Article Saving:{' '}
<a
target="_blank"
aria-label="Wallabag Article Saver"
href="https://github.com/wallabag/wallabag"
rel="noreferrer"
>
Wallabag
</a>
</li>
<li>
Anonymous Email Forwarding:{' '}
<a
target="_blank"
aria-label="AnonAddy Email Forwarding"
href="https://anonaddy.com/"
rel="noreferrer"
>
AnonAddy
</a>
</li>
<li>
Email:{' '}
<a
target="_blank"
aria-label="ProtonMail"
href="https://protonmail.com"
rel="noreferrer"
>
ProtonMail
</a>
</li>
<li>
Notes:{' '}
<a
target="_blank"
aria-label="Standard Notes"
href="https://standardnotes.org/"
rel="noreferrer"
>
Standard Notes
</a>
</li>
<li>
VPN:{' '}
<a
target="_blank"
aria-label="Mullvad VPN"
href="https://mullvad.net/en/"
rel="noreferrer"
>
Mullvad VPN
</a>
</li>
</ul>
</div>
<div>
<h3>NAS Servers for Self Hosting:</h3>
<ul>
<li>
<a
target="_blank"
aria-label="Synology NAS"
href="https://synology.com/"
rel="noreferrer"
>
Synology
</a>
: An easy, not cheap, local solution for Google Services like Drive,
Drive, Photos, Calendar, other services using Docker, etc. (Yes I
should use{' '}
<a
target="_blank"
aria-label="NextCloud Local Hosting Service"
href="https://nextcloud.com/"
rel="noreferrer"
>
NextCloud
</a>
...maybe eventually)
</li>
<li>
Mac Mini: Used as a{' '}
<a
target="_blank"
aria-label="Plex"
href="https://www.plex.tv/"
rel="noreferrer"
>
Plex Server
</a>{' '}
for my Movies/TV/Music until I build a dedicated NAS and maybe
switch to{' '}
<a
target="_blank"
aria-label="JellyFin"
href="https://jellyfin.org/"
rel="noreferrer"
>
JellyFin
</a>{' '}
once it is more mature.
</li>
</ul>
</div>
<div>
<h3>Software Deployed:</h3>
<ul>
<li>
<a
target="_blank"
aria-label="Traefik Cloud Proxy"
href="https://github.com/traefik/traefik"
rel="noreferrer"
>
Traefik
</a>
: A HTTP reverse proxy and load balancer.
</li>
<li>
<a
target="_blank"
aria-label="Syncthing File Synchronization"
href="https://github.com/syncthing/syncthing"
rel="noreferrer"
>
Syncthing
</a>
: An open source file synchronization program.
</li>
</ul>
</div>
</div> </div>
<style lang="postcss"> <style lang="postcss">
div {
margin-bottom: 2rem;
}
</style> </style>

View file

@ -1 +0,0 @@
export const prerender = true;