mirror of
https://github.com/Lissy93/personal-security-checklist.git
synced 2024-12-28 00:39:39 -05:00
Adds articles page and functionality, adds about page, updates nav, updates hero, more icons, improved checklist, and Tailwind prose
This commit is contained in:
parent
d29d5e2664
commit
f4bb43ab40
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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`,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
47
web/src/data/articles.ts
Normal 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;
|
@ -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>
|
||||
|
109
web/src/routes/about/about-content.ts
Normal file
109
web/src/routes/about/about-content.ts
Normal 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.
|
||||
`;
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
7
web/src/routes/article/[slug]/article.module.css
Normal file
7
web/src/routes/article/[slug]/article.module.css
Normal file
@ -0,0 +1,7 @@
|
||||
.psc_article {
|
||||
img {
|
||||
display: inline;
|
||||
margin: 0 auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
100
web/src/routes/article/[slug]/index.tsx
Normal file
100
web/src/routes/article/[slug]/index.tsx
Normal 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>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
29
web/src/routes/article/index.tsx
Normal file
29
web/src/routes/article/index.tsx
Normal 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>
|
||||
);
|
||||
});
|
@ -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>
|
||||
))}
|
||||
|
@ -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 />
|
||||
|
@ -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: {
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user