Adds articles page and functionality, adds about page, updates nav, updates hero, more icons, improved checklist, and Tailwind prose

This commit is contained in:
Alicia Sykes 2024-02-09 22:51:32 +00:00
parent d29d5e2664
commit f4bb43ab40
17 changed files with 683 additions and 93 deletions

View File

@ -35,6 +35,7 @@
},
"dependencies": {
"@builder.io/qwik": "^1.1.4",
"@tailwindcss/typography": "^0.5.10",
"chart.js": "^4.4.1",
"marked": "^12.0.0",
"progressbar.js": "^1.1.1",

View File

@ -124,6 +124,31 @@ const getSvgPath = (icon: string) => {
vb: "0 0 512 512",
path: "M0 128C0 92.7 28.7 64 64 64H370.7c17 0 33.3 6.7 45.3 18.7L566.6 233.4c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6L416 429.3c-12 12-28.3 18.7-45.3 18.7H64c-35.3 0-64-28.7-64-64V128zm143 47c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z",
};
case 'mastodon':
return {
vb: "0 0 512 512",
path: "M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z",
};
case 'twitter':
return {
vb: "0 0 512 512",
path: "M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z",
};
case 'hub':
return {
vb: "0 0 512 512",
path: "M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z",
};
case 'dev':
return {
vb: "0 0 512 512",
path: "M120.1 208.3c-3.9-2.9-7.8-4.4-11.7-4.4H91v104.5h17.5c3.9 0 7.8-1.5 11.7-4.4 3.9-2.9 5.8-7.3 5.8-13.1v-69.7c0-5.8-2-10.2-5.8-13.1zM404.1 32H43.9C19.7 32 .1 51.6 0 75.8v360.4C.1 460.4 19.7 480 43.9 480h360.2c24.2 0 43.8-19.6 43.9-43.8V75.8c-.1-24.2-19.7-43.8-43.9-43.8zM154.2 291.2c0 18.8-11.6 47.3-48.4 47.3h-46.4V173h47.4c35.4 0 47.4 28.5 47.4 47.3l0 70.9zm100.7-88.7H201.6v38.4h32.6v29.6H201.6v38.4h53.3v29.6h-62.2c-11.2 .3-20.4-8.5-20.7-19.7V193.7c-.3-11.2 8.6-20.4 19.7-20.7h63.2l0 29.5zm103.6 115.3c-13.2 30.8-36.9 24.6-47.4 0l-38.5-144.8h32.6l29.7 113.7 29.6-113.7h32.6l-38.5 144.8z",
};
case 'linkedin':
return {
vb: "0 0 512 512",
path: "M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z",
};
default:
return { vb: "", path: "" }; // Default path or a placeholder icon
}

View File

@ -1,11 +1,11 @@
import { component$ } from "@builder.io/qwik";
import Icon from "../core/icon";
import Icon from "~/components/core/icon";
export default component$(() => {
return (
<div class="hero bg-front mb-16 shadow-sm">
<div class="hero-content text-center">
<div class="hero mb-8 mx-auto xl:max-w-7xl max-w-6xl w-full xl:px-10">
<div class="hero-content text-center bg-front shadow-sm lg:rounded-xl w-full">
<div class="max-w-2xl flex flex-col place-items-center">
<p>The Ultimate</p>
<h1 class="text-5xl font-bold">Personal Security Checklist</h1>

View File

@ -4,6 +4,7 @@ import Icon from "~/components/core/icon";
import { data } from '~/mock-data';
import type { Section } from '~/types/PSC';
import { useTheme } from '~/store/theme-store';
import articles from '~/data/articles';
export default component$(() => {
@ -22,7 +23,7 @@ export default component$(() => {
</div>
<a href="/" class="btn btn-ghost text-xl flex capitalize">
<label for="my-drawer-3" aria-label="open sidebar" class="tooltip tooltip-bottom" data-tip="View all Pages"><Icon class="mr-2" icon="shield" width={28} height={28} /></label>
<h1>Personal Security Checklist</h1>
<h1>Digital Defense</h1>
</a>
</div>
<div class="flex-none hidden md:flex">
@ -34,12 +35,6 @@ export default component$(() => {
Checklists
</summary>
<ul class="p-2 bg-base-100 rounded-t-none z-10">
<li>
<a href="/checklist">
<Icon class="mr-2" icon="all" width={16} height={16} />
View All
</a>
</li>
{data.map((item: Section, index: number) => (
<li key={`checklist-nav-${index}`} class={`hover:bg-${item.color}-600 hover:bg-opacity-15`}>
<a href={`/checklist/${item.slug}`}>
@ -56,11 +51,6 @@ export default component$(() => {
<Icon icon="github" width={16} height={16} />GitHub
</a>
</li>
{/* <li>
<a href="https://apps.aliciasykes.com" class="tooltip flex tooltip-bottom" data-tip="Other projects by Alicia Sykes">
<Icon icon="code" width={24} height={16} />More
</a>
</li> */}
</ul>
<div class="tooltip tooltip-bottom" data-tip="Theme">
<label class="cursor-pointer grid place-items-center">
@ -84,10 +74,12 @@ export default component$(() => {
<ul class="rounded-box menu p-4 w-80 min-h-full bg-base-200">
<h2 class="flex text-primary">
<Icon class="mr-2" icon="shield" width={16} height={16} />
Personal Security Checklist
Digital Defense
</h2>
<li><a href="/"><Icon class="mr-2" icon="homepage" width={16} height={16} />Home</a></li>
<li><a href="/"><Icon class="mr-2" icon="github" width={16} height={16} />GitHub</a></li>
<li><a href="https://github.com/lissy93/personal-security-checklist">
<Icon class="mr-2" icon="github" width={16} height={16} />GitHub</a>
</li>
<li>
<a href="/checklist"><Icon class="mr-2" icon="all" width={16} height={16} />Checklists</a>
<ul>
@ -106,8 +98,11 @@ export default component$(() => {
<Icon class="mr-2" icon="articles" width={16} height={16} />Articles
</a>
<ul>
<li><a href="/article/1">Article 1</a></li>
<li><a href="/article/2">Article 2</a></li>
{articles.map(article => (
<li key={article.slug}>
<a href={`/article/${article.slug}`}>{article.title}</a>
</li>
))}
</ul>
</li>
<li>
@ -124,29 +119,18 @@ export default component$(() => {
</ul>
<ul>
<li>
<a href="#">Author</a>
<a href="/about#author">Author</a>
<ul>
<li><a href="#">Contact</a></li>
<li>
<a href="#">Socials</a>
<ul>
<li><a href="">GitHub</a></li>
<li><a href="">Twitter</a></li>
<li><a href="">Mastodon</a></li>
</ul>
</li>
<li><a href="https://aliciasykes.com/contact">Contact</a></li>
<li>
<a href="https://apps.aliciasykes.com">More Apps</a>
<ul>
<li><a href="#">Web-Check</a></li>
<li><a href="#">Dashy</a></li>
<li><a href="#">Portainer-Templates</a></li>
<li><a href="#">AdGuardian</a></li>
<li><a href="#">Bug-Bounties</a></li>
<li><a href="#">Awesome Privacy</a></li>
<li><a href="#">Email Comparison</a></li>
<li><a href="#">Git-In</a></li>
</ul>
</li>
<li class="flex flex-row">
<a href="https://github.com/lissy93"><Icon icon="hub" width={16} height={16} /></a>
<a href="https://x.com/lissy_sykes"><Icon icon="twitter" width={16} height={16} /></a>
<a href="https://mastodon.social/@lissy93"><Icon icon="mastodon" width={16} height={16} /></a>
<a href="https://dev.to/lissy93"><Icon icon="dev" width={16} height={16} /></a>
<a href="https://linkedin.com/in/aliciasykes"><Icon icon="linkedin" width={16} height={16} /></a>
</li>
</ul>
</li>

View File

@ -17,6 +17,8 @@ export default component$(() => {
const checklists = useContext(ChecklistContext);
// Completed items, from local storage
const [checkedItems] = useLocalStorage('PSC_PROGRESS', {});
// Ignored items, from local storage
const [ignoredItems] = useLocalStorage('PSC_IGNORED', {});
// Store to hold calculated progress results
const totalProgress = useSignal({ completed: 0, outOf: 0 });
// Ref to the radar chart canvas
@ -33,15 +35,19 @@ export default component$(() => {
if (!checkedItems.value || !sections.length) {
return { completed: 0, outOf: 0 };
}
const totalItems = sections.reduce((total: number, section: Section) => total + section.checklist.length, 0);
let totalItems = sections.reduce((total: number, section: Section) => total + section.checklist.length, 0);
let totalComplete = 0;
sections.forEach((section: Section) => {
section.checklist.forEach((item) => {
const id = item.point.toLowerCase().replace(/ /g, '-');
const isComplete = checkedItems.value[id];
const isIgnored = ignoredItems.value[id];
if (isComplete) {
totalComplete++;
}
if (isIgnored) {
totalItems--;
}
});
});
return { completed: totalComplete, outOf: totalItems };
@ -187,9 +193,9 @@ export default component$(() => {
// Wait on each set to resolve, and return the final data object
return Promise.all([
buildDataForPriority('recommended', 'hsl(158 64% 52%/75%)'),
buildDataForPriority('optional', 'hsl(43 96% 56%/75%)'),
buildDataForPriority('advanced', 'hsl(0 91% 71%/75%)'),
buildDataForPriority('optional', 'hsl(43 96% 56%/75%)'),
buildDataForPriority('recommended', 'hsl(158 64% 52%/75%)'),
]).then(datasets => ({
labels,
datasets,
@ -239,7 +245,7 @@ export default component$(() => {
},
tooltip: {
callbacks: {
label: (ctx) => `Completed ${ctx.parsed.r}% of ${ctx.dataset.label || ''} items`,
label: (ctx) => `Completed ${Math.round(ctx.parsed.r)}% of ${ctx.dataset.label || ''} items`,
}
}
},

View File

@ -1,33 +1,88 @@
import { component$ } from "@builder.io/qwik";
import type { Section } from '../../types/PSC';
import Icon from '../core/icon';
import { $, component$, useOnWindow, useSignal } from "@builder.io/qwik";
import { useLocalStorage } from "~/hooks/useLocalStorage";
import type { Checklist, Section } from '~/types/PSC';
import Icon from '~/components/core/icon';
import styles from './psc.module.css';
export default component$((props: { sections: Section[] }) => {
// Create signals to store the number of items done or ignored per section
const completions = useSignal<number[]>();
const done = useSignal<number[]>();
// Get the IDs of completed and ignore items from local storage
const [checked] = useLocalStorage('PSC_PROGRESS', {});
const [ignored] = useLocalStorage('PSC_IGNORED', {});
/**
* Get the percentage of completion for a given section
* using completion data from local storage, and disregarding ignored items
*/
const getPercentCompletion = $((section: Section): number => {
const id = (item: Checklist) => item.point.toLowerCase().replace(/ /g, '-')
const total = section.checklist.filter((item) => !ignored.value[id(item)]).length;
const done = section.checklist.filter((item) => checked.value[id(item)]).length;
return Math.round((done / total) * 100);
});
// On load (in browser only), calculate and set completion data for sections
useOnWindow('load', $(async () => {
// Percentage completion, per section
completions.value = await Promise.all(props.sections.map(section =>
getPercentCompletion(section),
));
// Count of completed items, per section
done.value = await Promise.all(props.sections.map(section =>
section.checklist.filter(
(item) => checked.value[item.point.toLowerCase().replace(/ /g, '-')],
).length
));
}));
return (
<div class={[styles.container, 'grid',
'mx-auto mt-8 px-4 gap-7', 'xl:px-10 xl:max-w-7xl',
'transition-all', 'max-w-6xl w-full']}>
{props.sections.map((section: Section) => (
{props.sections.map((section: Section, index: number) => (
<a key={section.slug}
href={`/checklist/${section.slug}`}
class={['card card-side bg-front bg-opacity-25 shadow-md transition-all px-2',
`outline-offset-2 outline-${section.color}-400`,
`hover:outline hover:outline-10 hover:outline-offset-4 hover:bg-opacity-15 hover:bg-${section.color}-600`]}
class={[
'card card-side bg-front bg-opacity-25 shadow-md transition-all px-2',
`outline-offset-2 outline-${section.color}-400`,
'hover:outline hover:outline-10 hover:outline-offset-4 hover:bg-opacity-15',
`hover:bg-${section.color}-600`
]}
>
<div class="flex-shrink-0 flex flex-col py-4 h-auto items-stretch justify-evenly">
<Icon icon={section.icon || 'star'} color={section.color} />
<p class={`text-${section.color}-400 pt-2 pb-0 px-0 mx-0 my-0`}>0/{section.checklist.length} Done</p>
{(done.value && done.value[index]) ? (
<p class={`text-${section.color}-400 pt-2 pb-0 px-0 mx-0 my-0`}>
{done.value[index]}/{section.checklist.length} Done
</p>
) : (
<p class={`text-${section.color}-400 pt-2 pb-0 px-0 mx-0 my-0`}>
{section.checklist.length} Items
</p>
)}
</div>
<div class="card-body flex-grow py-2 pl-4 pr-0">
<h2 class={`card-title text-${section.color}-400 hover:text-${section.color}-500`}>{section.title}</h2>
<h2 class={`card-title text-${section.color}-400 hover:text-${section.color}-500`}>
{section.title}
</h2>
<p class="p-0">{section.description}</p>
{/* <div class="card-actions justify-end">
<button class={`btn text-base-100 bg-${section.color}-400 hover:bg-${section.color}-600`}>View Checklist</button>
</div> */}
{(completions.value && completions.value[index]) ? (
<div
class={['radial-progress absolute right-2 top-2 scale-75', `text-${section.color}-400`]}
style={`--value:${completions.value[index]}; --size: 2.5rem;`}
role="progressbar">
<span class="text-xs">{completions.value[index]}%</span>
</div>
) : (
<span class="absolute right-2 top-2 opacity-30 text-xs">
Not yet started
</span>
)}
</div>
</a>
))}

47
web/src/data/articles.ts Normal file
View File

@ -0,0 +1,47 @@
interface Article {
title: string;
description: string;
slug: string;
markdown: string;
warningMessage?: string;
}
const articles: Article[] = [
{
title: 'Why security matters?',
description: 'Why your personal digital security and privacy needs to be taken seriously.',
slug: 'importance-of-digital-security',
markdown: 'https://raw.githubusercontent.com/Lissy93/personal-security-checklist/old-version/0_Why_It_Matters.md',
},
{
title: 'Security List: Short Version',
description: 'Main lists too long? Here\'s the TL;DR',
slug: 'short-list',
markdown: 'https://raw.githubusercontent.com/Lissy93/personal-security-checklist/old-version/2_TLDR_Short_List.md',
},
{
title: 'Helpful Links',
description: 'Directory of links to additional tools, resources and information.',
slug: 'helpful-links',
markdown: 'https://raw.githubusercontent.com/Lissy93/personal-security-checklist/old-version/4_Privacy_And_Security_Links.md',
warningMessage: 'This article was written in 2020, and needs updating.',
},
{
title: 'Security Gadgets',
description: 'Handy hardware devices that can help protect your privacy and security.',
slug: 'privacy-gadgets',
markdown: 'https://raw.githubusercontent.com/Lissy93/personal-security-checklist/old-version/6_Privacy_and-Security_Gadgets.md',
warningMessage: 'This article is outdated and may contain incorrect information. '
+'It is recommended to verify the information before using any of the products listed.',
},
{
title: 'Privacy-Respecting Software',
description: 'The ultimate list of privacy-respecting software alternatives to popular apps and services.',
slug: 'awesome-privacy',
markdown: 'https://raw.githubusercontent.com/Lissy93/awesome-privacy/main/README.md',
warningMessage: 'This resource has moved! You can now access it at github.com/Lissy93/awesome-privacy',
},
];
export default articles;

View File

@ -28,7 +28,7 @@ export default component$(() => {
<RouterHead />
<ServiceWorkerRegister />
</head>
<body lang="en" data-theme="night" class="flex flex-col justify-between min-h-screen">
<body lang="en" data-theme="dark" class="flex flex-col justify-between min-h-screen">
<RouterOutlet />
</body>
</QwikCityProvider>

View File

@ -0,0 +1,109 @@
export const intro = [
`The objective of this project is to give you practical guidance on how to improve your digital security, and protect your privacy online.`,
`
The checklist is a living document, and will be updated regularly to reflect the latest threats and best practices.
This is made possible by open sourcing the content, and making it a community maintained resource,
meaning that anyone can suggest changes, make additions or update the guidance.
All edits are then reviewed by maintainers before being merged and going live.
`];
export const projects = [
{
title: 'Web-Check',
description: 'OSINT tool for analysing any website',
icon: 'https://icon.horse/icon/web-check.xyz',
link: 'https://github.com/lissy93/web-check',
},
{
title: 'Dashy',
description: 'Dashboard app, for organising your self-hosted services',
icon: 'https://dashy.to/img/dashy.png',
link: 'https://github.com/lissy93/dashy',
},
{
title: 'Email Comparison',
description: 'Objective comparison of private/secure mail providers',
icon: 'https://email-comparison.as93.net/favicon.png',
link: 'https://github.com/lissy93/email-comparison',
},
{
title: 'Awesome Privacy',
description: 'A list of privacy-respscting software and services',
icon: 'https://awesome-privacy.xyz/awesome-privacy.png',
link: 'https://github.com/lissy93/awesome-privacy',
},
{
title: 'Portainer-Templates',
description: 'Compiled repository of 1-click Docker apps for self-hosting',
icon: 'https://portainer-templates.as93.net/favicon.png',
link: 'https://github.com/lissy93/portainer-templates',
},
{
title: 'AdGuardian',
description: 'CLI tool for monitoring your networks traffic and AdGuard DNS stats',
icon: 'https://adguardian.as93.net/favicon.png',
link: 'https://github.com/lissy93/adguardian-term',
},
{
title: 'Bug-Bounties',
description: 'Database of websites which accept responsible vulnerability discolsure',
icon: 'https://bug-bounties.as93.net/favicon.png',
link: 'https://github.com/lissy93/bug-bounties',
},
{
title: 'Git-In',
description: 'Tools and resources to help beginners get into open source',
icon: 'https://www.git-in.to/favicon.png',
link: 'https://github.com/lissy93/git-in',
},
];
export const socials = [
{
title: 'GitHub',
icon: 'hub',
link: 'https://github.com/lissy93',
},
{
title: 'Twitter',
icon: 'twitter',
link: 'https://x.com/lissy_sykes',
},
{
title: 'Mastodon',
icon: 'mastodon',
link: 'https://mastodon.social/@lissy93',
},
{
title: 'Dev',
icon: 'dev',
link: 'https://dev.to/lissy93',
},
{
title: 'LinkedIn',
icon: 'linkedin',
link: 'https://linkedin.com/in/aliciasykes',
},
];
export const license = `
The MIT License (MIT)
Copyright (c) Alicia Sykes <alicia@aliciasykes.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included install
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANT ABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`;

View File

@ -1,45 +1,212 @@
import { component$ } from "@builder.io/qwik";
import { component$, useResource$, Resource } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
import Icon from "~/components/core/icon";
import { projects, socials, intro, license } from './about-content';
export default component$(() => {
interface Contributor {
login: string;
avatar_url: string;
avatarUrl: string;
html_url: string;
contributions: number;
name: string;
}
const contributorsResource = useResource$<Contributor[]>(async () => {
const url = 'https://api.github.com/repos/lissy93/personal-security-checklist/contributors?per_page=100';
const response = await fetch(url);
if (!response.ok) {
throw new Error('Failed to fetch contributors');
}
return await response.json();
});
const sponsorsResource = useResource$<Contributor[]>(async () => {
const url = 'https://github-sponsors.as93.workers.dev/lissy93';
const response = await fetch(url);
if (!response.ok) {
throw new Error('Failed to fetch sponsors');
}
return await response.json();
});
return (
<div class="m-4 md:mx-16">
<article class="bg-back p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-md">
<h2 class="text-2xl">About the Security Checklist</h2>
<h2 class="text-3xl mb-2">About the Security Checklist</h2>
{intro.map((paragraph, index) => (
<p class="mb-2" key={index}>{paragraph}</p>
))}
</article>
<div class="divider"></div>
<article class="bg-back p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-md">
<h2 class="text-2xl">About the Author</h2>
<p>
This project was originally started by me, Alicia Sykes- with a lot of help from the community.
</p>
<br />
<img class="ml-6 rounded-lg float-right" width="200" height="141" alt="Alicia Sykes Thames" src="https://i.ibb.co/s5SM1K7/DSC-0031-4.jpg" />
<p>
I write apps which aim to help people escape big tech, secure their data,
and protect their privacy.
</p>
<br />
<p>
I have a particular interest in self-hosting, Linux and OSINT.
So if this type of stuff interests you, check out these other projects that I maintain:
</p>
<br />
<ul class="list-disc pl-8">
<li>Web-Check - OSINT tool for analysing any website</li>
<li>Dashy - Dashboard app, for organising your self-hosted services</li>
<li>Portainer-Templates - Compiled repository of 1-click Docker apps for self-hosting</li>
<li>AdGuardian - CLI tool for monitoring your networks traffic and AdGuard DNS stats</li>
<li>Bug-Bounties - Database of websites which accept responsible vulnerability discolsure</li>
<li>Awesome Privacy - A list of privacy-respscting software and services</li>
<li>Email Comparison - Objective comparison of private/secure mail providers</li>
<li>Git-In - Tools and resources to help beginners get into open source</li>
</ul>
<br />
<p>For a full list of projects I've published, see <a href="https://apps.aliciasykes.com/">apps.aliciasykes.com</a>, or follow me on GitHub (I'm <a href="https://github.com/lissy93">Lissy93</a>).</p>
<h2 class="text-3xl mb-2">Credits</h2>
<h3 class="text-2xl mb-2">Sponsors</h3>
<p>
Huge thanks to the following sponsors, for their ongoing support 💖
</p>
<div class="flex flex-wrap gap-4 my-4 mx-auto">
<Resource
value={sponsorsResource}
onPending={() => <p>Loading...</p>}
onResolved={(contributors: Contributor[]) => (
contributors.map((contributor: Contributor) => (
<a
class="w-16 tooltip tooltip-bottom"
href={contributor.html_url || `https://github.com/${contributor.login}`}
target="_blank"
rel="noopener noreferrer"
key={contributor.login}
data-tip={`Thank you @${contributor.login}`}
>
<img
class="avatar rounded"
width="64" height="64"
src={contributor.avatar_url || contributor.avatarUrl}
alt={contributor.login}
/>
<p
class="text-ellipsis overflow-hidden w-max-16 mx-auto line-clamp-2"
>{contributor.name || contributor.login}</p>
</a>
))
)}
/>
</div>
<div class="divider"></div>
<h3 class="text-2xl mb-2">Contributors</h3>
<p>
This project exists thanks to all the people who've helped build and maintain it.<br />
Special thanks to the below, top-100 contributors 🌟
</p>
<div class="flex flex-wrap gap-4 my-4 mx-auto">
<Resource
value={contributorsResource}
onPending={() => <p>Loading...</p>}
onResolved={(contributors: Contributor[]) => (
contributors.map((contributor: Contributor) => (
<a
class="w-16 tooltip tooltip-bottom"
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
key={contributor.login}
data-tip={`@${contributor.login} has contributed ${contributor.contributions} times\n\nClick to view their profile`}
>
<img
class="avatar rounded"
width="64" height="64"
src={contributor.avatar_url}
alt={contributor.login}
/>
<p
class="text-ellipsis overflow-hidden w-max-16 mx-auto"
>{contributor.login}</p>
</a>
))
)}
/>
</div>
</article>
<div class="divider"></div>
<article class="bg-back p-8 mx-auto max-w-[1200px] my-8 rounded-lg shadow-md">
<h2 class="text-3xl mb-2" id="author">About the Author</h2>
<p>
This project was originally started by
me, <a href="https://aliciasykes.com" class="link link-primary">Alicia Sykes</a>
- with a lot of help from the community.
</p>
<br />
<div class="ml-4 float-right">
<img class="rounded-lg" width="180" height="240" alt="Alicia Sykes" src="https://i.ibb.co/fq10qhL/DSC-0597.jpg" />
<div class="flex gap-2 my-2 justify-between">
{
socials.map((social, index) => (
<a key={index} href={social.link}>
<Icon icon={social.icon} width={24} height={24} />
</a>
))
}
</div>
</div>
<p class="text-lg italic font-thin">
I write apps which aim to help people <b>escape big tech, secure their data, and protect their privacy</b>.
</p>
<br />
<p>
I have a particular interest in self-hosting, Linux, security and OSINT.<br />
So if this type of stuff interests you, check out these other projects:
</p>
<ul class="list-disc pl-8">
{
projects.map((project, index) => (
<li key={index}>
<img class="rounded inline mr-1" width="20" height="20" alt={project.title} src={project.icon} />
<a href={project.link} class="link link-secondary" target="_blank" rel="noreferrer">
{project.title}
</a> - {project.description}
</li>
))
}
</ul>
<br />
<p>
For a more open source apps I've published,
see <a href="https://apps.aliciasykes.com/" class="link link-primary">apps.aliciasykes.com</a>,
or <a href="https://github.com/lissy93" class="link link-primary">follow me on GitHub</a>
</p>
</article>
<div class="divider"></div>
<article class="bg-back p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-md">
<h2 class="text-3xl mb-2">License</h2>
<p>
This project is split-licensed, with the checklist content (located
in <a class="link" href="https://github.com/Lissy93/personal-security-checklist/blob/HEAD/personal-security-checklist.yml">
<code>personal-security-checklist.yml</code>
</a>) being licensed
under <b><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></b>.
And everything else (including all the code), licensed
under <b><a href="https://gist.github.com/Lissy93/143d2ee01ccc5c052a17">MIT</a></b>.
</p>
<pre class="bg-front whitespace-break-spaces rounded text-xs my-2 mx-auto p-2">
{license}
</pre>
<details class="collapse">
<summary class="collapse-title">
<h3 class="mt-2">What does this means for you?</h3>
</summary>
<div class="collapse-content">
<p class="mb-2">
This means that for everything (except the checklist YAML file), you have almost unlimited freedom to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
All that we ask is that you include the original copyright notice and permission notice in any copies of the software
</p>
<p class="mb-2">
And for the security-list content you can share and adapt this content as long as you give appropriate credit,
don't use it for commercial purposes, and distribute your contributions under the same license.
</p>
</div>
</details>
</article>
</div>
);
});

View File

@ -0,0 +1,7 @@
.psc_article {
img {
display: inline;
margin: 0 auto;
border-radius: 4px;
}
}

View File

@ -0,0 +1,100 @@
// src/routes/articles/[slug].tsx
import { component$, Resource, useResource$, useStore } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
import { marked } from "marked";
import articles from '~/data/articles';
import styles from './article.module.css';
export default component$(() => {
const location = useLocation();
const store = useStore({ article: null, notFound: false });
const slug = location.params.slug;
const article = articles.find(a => a.slug === slug);
const parseMarkdown = (text: string | undefined): string => {
if (!text) return '';
// Custom renderer
const renderer = new marked.Renderer();
// Override function to handle headings
renderer.heading = (text, level) => {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `<h${level} id="${escapedText}">${text}</h${level}>`;
};
// Override function to handle links
renderer.link = (href, title, text) => {
if (href.startsWith('/')) {
href = `https://github.com/Lissy93/personal-security-checklist/blob/old-version/${href}`;
}
title = title ? `title="${title}"` : '';
return `<a href="${href}" ${title} target="_blank" rel="noopener noreferrer">${text}</a>`;
};
// Sanitize the input to remove <script> tags
const sanitizeHtml = (html: string): string => {
return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
};
// Configure marked with the custom renderer
marked.use({ renderer });
// Parse the markdown, then sanitize the HTML to remove <script> tags
const rawHtml = marked.parse(text, { async: false}) as string;
const sanitizedHtml = sanitizeHtml(rawHtml);
return sanitizedHtml;
};
const articleResource = useResource$<string>(async () => {
if (!article) {
store.notFound = true;
return '';
}
const response = await fetch(article.markdown);
if (!response.ok) {
store.notFound = true;
return '';
}
return response.text();
});
if (store.notFound) {
return <div>404 Article Not Found</div>;
}
return (
<Resource
value={articleResource}
onResolved={(content) => (
<article class={[
'prose bg-back my-4 mx-auto rounded-lg shadow-lg p-8',
'max-w-max sm:w-11/12 md:w-4/5 xl:w-3/4 2xl:2/4',
styles.psc_article
]}>
{article?.warningMessage && (
<div role="alert" class="alert alert-warning opacity-75 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span><b>Warning</b>: {article.warningMessage}</span>
</div>
)}
<div dangerouslySetInnerHTML={parseMarkdown(content)}></div>
</article>
)}
/>
);
});

View File

@ -0,0 +1,29 @@
// src/routes/articles/index.tsx
import { component$ } from '@builder.io/qwik';
import articles from '~/data/articles';
export default component$(() => {
return (
<div class="px-8 py-4">
<div class="bg-back shadow-md rounded-box min-h-96 px-4 py-8">
<h2 class="text-4xl mb-4">Articles</h2>
<ul class="flex flex-col gap-4">
{articles.map(article => (
<li key={article.slug}
class="rounded-box bg-front shadow-md p-4 max-w-96 drop-shadow-md
transition hover:drop-shadow-xl hover:scale-105"
>
<a href={`/article/${article.slug}`}>
<h3 class="text-2xl mb-2">{article.title}</h3>
<p class="text-lg">{article.description}</p>
</a>
</li>
))}
</ul>
</div>
</div>
);
});

View File

@ -1,22 +1,54 @@
import { component$, useContext } from "@builder.io/qwik";
import { ChecklistContext } from '~/store/checklist-context';
import { useLocalStorage } from "~/hooks/useLocalStorage";
import type { Section } from "~/types/PSC";
export default component$(() => {
const checklists = useContext(ChecklistContext);
const [completed, setCompleted] = useLocalStorage('PSC_PROGRESS', {});
return (
<main class="p-8">
<div class="join join-vertical w-full">
{checklists.value.map((section: Section, index: number) => (
<div key={index} class={['collapse collapse-plus bg-base-200 my-4', `border-double border-2 border-${section.color}-400`]}>
<input type="radio" name="my-accordion-3" />
<div class={['collapse-title text-xl font-medium', `bg-${section.color}-400`]}>
<h3 class="text-slate-700">{section.title}</h3>
<div class={['collapse-title text-xl font-medium']}>
<h3>{section.title}</h3>
</div>
<div class="collapse-content">
<p>hello</p>
{
section.checklist.map((item, index) => {
const pointId = item.point.toLowerCase().replace(/ /g, '-');
return (
<div key={index} class="flex justify-between">
<label class="flex items gap-2" for={`check-${index}`}>
<input
class="checkbox checkbox-sm"
id={`check-${index}`}
type="checkbox"
checked={completed.value[pointId] || false}
onClick$={() => {
const data = completed.value;
data[pointId] = !data[pointId];
setCompleted(data);
}}
/>
<span class="tooltip tooltip-bottom" data-tip={item.details}>{item.point}</span>
</label>
</div>
)
})
}
<div class="card-actions justify-end">
<a href={`/checklist/${section.slug}`}>
<button class={`btn text-base-100 bg-${section.color}-400 hover:bg-${section.color}-600`}>
View Full Checklist
</button>
</a>
</div>
</div>
</div>
))}

View File

@ -34,7 +34,7 @@ export default component$(() => {
<>
{/* <Header /> */}
<Navbar />
<main class="bg-base-100">
<main class="bg-base-100 min-h-full">
<Slot />
</main>
<Footer />

View File

@ -10,7 +10,7 @@ const applyCustomColors = (theme, front, back) => {
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
plugins: [require('daisyui')],
plugins: [require('daisyui'), require("@tailwindcss/typography")],
theme: {
extend: {
colors: {

View File

@ -608,6 +608,16 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz#2c1fb69e02a3f1506f52698cfdc3a8b6386df9a6"
integrity sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==
"@tailwindcss/typography@^0.5.10":
version "0.5.10"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.10.tgz#2abde4c6d5c797ab49cf47610830a301de4c1e0a"
integrity sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
"@trysound/sax@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
@ -2240,6 +2250,16 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.castarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@ -2960,6 +2980,14 @@ postcss-nested@^6.0.1:
dependencies:
postcss-selector-parser "^6.0.11"
postcss-selector-parser@6.0.10:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.11:
version "6.0.15"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535"