1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 06:45:23 -04:00

refactor notes directory and front matter

This commit is contained in:
Jake Jarvis 2025-03-06 16:52:11 -05:00
parent 8b2e513ca9
commit 6706aa68ab
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
215 changed files with 471 additions and 615 deletions

View File

@ -4,7 +4,7 @@ import Video from "../../components/Video";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata } from "next"; import type { Metadata } from "next";
import thumbnail from "../../public/static/images/birthday/thumb.png"; import thumbnail from "./thumbnail.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "🎉 Cranky Birthday Boy on VHS Tape 📼", title: "🎉 Cranky Birthday Boy on VHS Tape 📼",
@ -29,8 +29,8 @@ export default function Page() {
<Content> <Content>
<Video <Video
src={{ src={{
webm: "/static/images/birthday/birthday.webm", webm: "/static/birthday/birthday.webm",
mp4: "/static/images/birthday/birthday.mp4", mp4: "/static/birthday/birthday.mp4",
}} }}
poster={thumbnail.src} poster={thumbnail.src}
/> />

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -9,7 +9,7 @@ import { UnorderedList, ListItem } from "../../components/List";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata } from "next"; import type { Metadata } from "next";
import cliImg from "../../public/static/images/cli/screenshot.png"; import cliImg from "./images/screenshot.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "CLI", title: "CLI",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -5,7 +5,7 @@ import Video from "../../components/Video";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata } from "next"; import type { Metadata } from "next";
import thumbnail from "../../public/static/images/hillary/thumb.png"; import thumbnail from "./thumbnail.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "My Brief Apperance in Hillary Clinton's DNC Video", title: "My Brief Apperance in Hillary Clinton's DNC Video",
@ -30,9 +30,9 @@ export default function Page() {
<Content> <Content>
<Video <Video
src={{ src={{
webm: "/static/images/hillary/convention-720p.webm", webm: "/static/hillary/convention-720p.webm",
mp4: "/static/images/hillary/convention-720p.mp4", mp4: "/static/hillary/convention-720p.mp4",
vtt: "/static/images/hillary/subs.en.vtt", vtt: "/static/hillary/subs.en.vtt",
}} }}
poster={thumbnail.src} poster={thumbnail.src}
/> />

View File

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -11,7 +11,7 @@ import "modern-normalize/modern-normalize.css"; // https://github.com/sindresorh
import "./themes.css"; import "./themes.css";
import "./global.css"; import "./global.css";
import meJpg from "../public/static/images/me.jpg"; import meJpg from "./me.jpg";
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(config.baseUrl), metadataBase: new URL(config.baseUrl),

View File

@ -5,7 +5,7 @@ import Video from "../../components/Video";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata } from "next"; import type { Metadata } from "next";
import thumbnail from "../../public/static/images/leo/thumb.png"; import thumbnail from "./thumbnail.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Facebook App on "The Lab with Leo Laporte"', title: 'Facebook App on "The Lab with Leo Laporte"',
@ -30,9 +30,9 @@ export default function Page() {
<Content> <Content>
<Video <Video
src={{ src={{
webm: "/static/images/leo/leo.webm", webm: "/static/leo/leo.webm",
mp4: "/static/images/leo/leo.mp4", mp4: "/static/leo/leo.mp4",
vtt: "/static/images/leo/subs.en.vtt", vtt: "/static/leo/subs.en.vtt",
}} }}
poster={thumbnail.src} poster={thumbnail.src}
/> />

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,11 +1,6 @@
import config from "../lib/config"; import config from "../lib/config";
import type { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
import chrome512Png from "../public/static/favicons/android-chrome-512x512.png";
import chrome192Png from "../public/static/favicons/android-chrome-192x192.png";
import maskable512Png from "../public/static/favicons/maskable-512x512.png";
import maskable192Png from "../public/static/favicons/maskable-192x192.png";
const manifest = (): MetadataRoute.Manifest => { const manifest = (): MetadataRoute.Manifest => {
return { return {
name: config.siteName, name: config.siteName,
@ -14,28 +9,9 @@ const manifest = (): MetadataRoute.Manifest => {
lang: config.siteLocale, lang: config.siteLocale,
icons: [ icons: [
{ {
src: chrome512Png.src, src: "/icon.png",
sizes: `${chrome512Png.width}x${chrome512Png.height}`, sizes: "any",
type: "image/png", type: "image/png",
purpose: "any",
},
{
src: chrome192Png.src,
sizes: `${chrome192Png.width}x${chrome192Png.height}`,
type: "image/png",
purpose: "any",
},
{
src: maskable512Png.src,
sizes: `${maskable512Png.width}x${maskable512Png.height}`,
type: "image/png",
purpose: "maskable",
},
{
src: maskable192Png.src,
sizes: `${maskable192Png.width}x${maskable192Png.height}`,
type: "image/png",
purpose: "maskable",
}, },
], ],
display: "browser", display: "browser",

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -11,8 +11,8 @@ export default async function Page() {
<div style={{ textAlign: "center" }}> <div style={{ textAlign: "center" }}>
<Video <Video
src={{ src={{
webm: "/static/images/angry-panda.webm", webm: "/static/not-found/angry-panda.webm",
mp4: "/static/images/angry-panda.mp4", mp4: "/static/not-found/angry-panda.mp4",
}} }}
autoplay autoplay
responsive={false} responsive={false}

View File

@ -6,7 +6,7 @@ import Time from "../../../components/Time";
import Comments from "../../../components/Comments"; import Comments from "../../../components/Comments";
import Loading from "../../../components/Loading"; import Loading from "../../../components/Loading";
import HitCounter from "./counter"; import HitCounter from "./counter";
import { getPostSlugs, getPostData } from "../../../lib/helpers/posts"; import { getPostSlugs, getFrontMatter } from "../../../lib/helpers/posts";
import { metadata as defaultMetadata } from "../../layout"; import { metadata as defaultMetadata } from "../../layout";
import config from "../../../lib/config"; import config from "../../../lib/config";
import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi"; import { FiCalendar, FiTag, FiEdit, FiEye } from "react-icons/fi";
@ -32,22 +32,22 @@ export async function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const { frontMatter } = await getPostData(slug); const frontmatter = await getFrontMatter(slug);
return { return {
title: frontMatter.title, title: frontmatter.title,
description: frontMatter.description, description: frontmatter.description,
openGraph: { openGraph: {
...defaultMetadata.openGraph, ...defaultMetadata.openGraph,
title: frontMatter.title, title: frontmatter.title,
url: `/notes/${slug}`, url: `/notes/${slug}`,
type: "article", type: "article",
authors: [config.authorName], authors: [config.authorName],
tags: frontMatter.tags, tags: frontmatter.tags,
publishedTime: frontMatter.date, publishedTime: frontmatter.date,
modifiedTime: frontMatter.date, modifiedTime: frontmatter.date,
images: frontMatter.image images: frontmatter.image
? [{ url: frontMatter.image, alt: frontMatter.title }] ? [{ url: frontmatter.image, alt: frontmatter.title }]
: defaultMetadata.openGraph?.images, : defaultMetadata.openGraph?.images,
}, },
alternates: { alternates: {
@ -59,17 +59,17 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
export default async function Page({ params }: { params: Promise<{ slug: string }> }) { export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params; const { slug } = await params;
const { frontMatter } = await getPostData(slug); const frontmatter = await getFrontMatter(slug);
const jsonLd: WithContext<Article> = { const jsonLd: WithContext<Article> = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Article", "@type": "Article",
name: frontMatter.title, name: frontmatter.title,
description: frontMatter.description || config.longDescription, description: frontmatter.description || config.longDescription,
url: frontMatter.permalink, url: frontmatter.permalink,
image: frontMatter.image, image: frontmatter.image,
datePublished: frontMatter.date, datePublished: frontmatter.date,
dateModified: frontMatter.date, dateModified: frontmatter.date,
author: { author: {
"@type": "Person", "@type": "Person",
name: config.authorName, name: config.authorName,
@ -77,7 +77,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
}, },
}; };
const { default: MDXContent } = await import(`../../../notes/${slug}.mdx`); const { default: MDXContent } = await import(`../../../notes/${slug}/index.mdx`);
return ( return (
<> <>
@ -85,17 +85,17 @@ export default async function Page({ params }: { params: Promise<{ slug: string
<div className={styles.meta}> <div className={styles.meta}>
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link href={`/notes/${frontMatter.slug}` as Route} plain className={styles.metaLink}> <Link href={`/notes/${frontmatter.slug}` as Route} plain className={styles.metaLink}>
<FiCalendar className={styles.metaIcon} /> <FiCalendar className={styles.metaIcon} />
<Time date={frontMatter.date} format="MMMM D, YYYY" /> <Time date={frontmatter.date} format="MMMM D, YYYY" />
</Link> </Link>
</div> </div>
{frontMatter.tags && ( {frontmatter.tags && (
<div className={styles.metaItem}> <div className={styles.metaItem}>
<FiTag className={styles.metaIcon} /> <FiTag className={styles.metaIcon} />
<span className={styles.metaTags}> <span className={styles.metaTags}>
{frontMatter.tags.map((tag) => ( {frontmatter.tags.map((tag) => (
<span key={tag} title={tag} className={styles.metaTag} aria-label={`Tagged with ${tag}`}> <span key={tag} title={tag} className={styles.metaTag} aria-label={`Tagged with ${tag}`}>
{tag} {tag}
</span> </span>
@ -106,8 +106,8 @@ export default async function Page({ params }: { params: Promise<{ slug: string
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
href={`https://github.com/${config.githubRepo}/blob/main/notes/${frontMatter.slug}.mdx`} href={`https://github.com/${config.githubRepo}/blob/main/notes/${frontmatter.slug}/index.mdx`}
title={`Edit "${frontMatter.title}" on GitHub`} title={`Edit "${frontmatter.title}" on GitHub`}
plain plain
className={styles.metaLink} className={styles.metaLink}
> >
@ -129,7 +129,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
> >
<FiEye className={styles.metaIcon} /> <FiEye className={styles.metaIcon} />
<Suspense fallback={<Loading boxes={3} width={20} />}> <Suspense fallback={<Loading boxes={3} width={20} />}>
<HitCounter slug={`notes/${frontMatter.slug}`} /> <HitCounter slug={`notes/${frontmatter.slug}`} />
</Suspense> </Suspense>
</div> </div>
</ErrorBoundary> </ErrorBoundary>
@ -138,8 +138,8 @@ export default async function Page({ params }: { params: Promise<{ slug: string
<h1 className={styles.title}> <h1 className={styles.title}>
<Link <Link
href={`/notes/${frontMatter.slug}` as Route} href={`/notes/${frontmatter.slug}` as Route}
dangerouslySetInnerHTML={{ __html: frontMatter.htmlTitle || frontMatter.title }} dangerouslySetInnerHTML={{ __html: frontmatter.htmlTitle || frontmatter.title }}
plain plain
className={styles.link} className={styles.link}
/> />
@ -149,9 +149,9 @@ export default async function Page({ params }: { params: Promise<{ slug: string
<MDXContent /> <MDXContent />
</Content> </Content>
{!frontMatter.noComments && ( {!frontmatter.noComments && (
<div id="comments"> <div id="comments">
<Comments title={frontMatter.title} /> <Comments title={frontmatter.title} />
</div> </div>
)} )}
</> </>

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 301 KiB

View File

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 244 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 286 KiB

View File

Before

Width:  |  Height:  |  Size: 622 KiB

After

Width:  |  Height:  |  Size: 622 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -12,20 +12,20 @@ import type { Metadata } from "next";
import { ComicNeue } from "../../lib/styles/fonts"; import { ComicNeue } from "../../lib/styles/fonts";
import styles from "./page.module.css"; import styles from "./page.module.css";
import img_wayback from "../../public/static/images/previously/wayback.png"; import img_wayback from "./images/wayback.png";
import img_2002_02 from "../../public/static/images/previously/2002_02.png"; import img_2002_02 from "./images/2002_02.png";
import img_2002_10 from "../../public/static/images/previously/2002_10.png"; import img_2002_10 from "./images/2002_10.png";
import img_2003_08 from "../../public/static/images/previously/2003_08.png"; import img_2003_08 from "./images/2003_08.png";
import img_2004_11 from "../../public/static/images/previously/2004_11.png"; import img_2004_11 from "./images/2004_11.png";
import img_2006_04 from "../../public/static/images/previously/2006_04.png"; import img_2006_04 from "./images/2006_04.png";
import img_2006_05 from "../../public/static/images/previously/2006_05.png"; import img_2006_05 from "./images/2006_05.png";
import img_2007_01 from "../../public/static/images/previously/2007_01.png"; import img_2007_01 from "./images/2007_01.png";
import img_2007_04 from "../../public/static/images/previously/2007_04.png"; import img_2007_04 from "./images/2007_04.png";
import img_2007_05 from "../../public/static/images/previously/2007_05.png"; import img_2007_05 from "./images/2007_05.png";
import img_2009_07 from "../../public/static/images/previously/2009_07.png"; import img_2009_07 from "./images/2009_07.png";
import img_2012_09 from "../../public/static/images/previously/2012_09.png"; import img_2012_09 from "./images/2012_09.png";
import img_2018_04 from "../../public/static/images/previously/2018_04.png"; import img_2018_04 from "./images/2018_04.png";
import img_2020_03 from "../../public/static/images/previously/2020_03.png"; import img_2020_03 from "./images/2020_03.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Previously on...", title: "Previously on...",

View File

Before

Width:  |  Height:  |  Size: 810 KiB

After

Width:  |  Height:  |  Size: 810 KiB

View File

@ -8,7 +8,7 @@ import { UnorderedList, ListItem } from "../../components/List";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata, Route } from "next"; import type { Metadata, Route } from "next";
import desktopImg from "../../public/static/images/uses/desktop.png"; import desktopImg from "./images/desktop.png";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "/uses", title: "/uses",

View File

Before

Width:  |  Height:  |  Size: 477 KiB

After

Width:  |  Height:  |  Size: 477 KiB

View File

@ -4,7 +4,7 @@ import CodeBlock from "../../components/CodeBlock/CodeBlock";
import { metadata as defaultMetadata } from "../layout"; import { metadata as defaultMetadata } from "../layout";
import type { Metadata } from "next"; import type { Metadata } from "next";
import backgroundImg from "../../public/static/images/zip/bg.jpg"; import backgroundImg from "./images/sundar.jpg";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "fuckyougoogle.zip", title: "fuckyougoogle.zip",

View File

@ -7,7 +7,7 @@ import type { ComponentPropsWithoutRef } from "react";
import styles from "./Header.module.css"; import styles from "./Header.module.css";
import selfieJpg from "../../public/static/images/selfie.jpg"; import selfieJpg from "./selfie.jpg";
export type HeaderProps = ComponentPropsWithoutRef<"header">; export type HeaderProps = ComponentPropsWithoutRef<"header">;

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -2,7 +2,7 @@ import { Feed } from "feed";
import { getAllPosts } from "./posts"; import { getAllPosts } from "./posts";
import config from "../config"; import config from "../config";
import meJpg from "../../public/static/images/me.jpg"; import meJpg from "../../app/me.jpg";
export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => { export const buildFeed = async (options: { type: "rss" | "atom" | "json" }): Promise<string> => {
// https://github.com/jpmonette/feed#example // https://github.com/jpmonette/feed#example

View File

@ -1,9 +1,7 @@
import path from "path"; import path from "path";
import fs from "fs/promises";
import glob from "fast-glob"; import glob from "fast-glob";
import pMap from "p-map"; import pMap from "p-map";
import pMemoize from "p-memoize"; import pMemoize from "p-memoize";
import matter from "gray-matter";
import { formatDate } from "./format-date"; import { formatDate } from "./format-date";
import config from "../config"; import config from "../config";
@ -23,21 +21,14 @@ export type FrontMatter = {
}; };
// returns front matter and the **raw & uncompiled** markdown of a given slug // returns front matter and the **raw & uncompiled** markdown of a given slug
export const getPostData = async ( export const getFrontMatter = async (slug: string): Promise<FrontMatter> => {
slug: string const { frontmatter } = await import(`../../${POSTS_DIR}/${slug}/index.mdx`);
): Promise<{
frontMatter: FrontMatter;
markdown: string;
}> => {
const { unified } = await import("unified"); const { unified } = await import("unified");
const { remarkParse, remarkSmartypants, remarkRehype, rehypeSanitize, rehypeStringify } = await import( const { remarkParse, remarkSmartypants, remarkRehype, rehypeSanitize, rehypeStringify } = await import(
"./remark-rehype-plugins" "./remark-rehype-plugins"
); );
const fullPath = path.join(process.cwd(), POSTS_DIR, `${slug}.mdx`);
const rawContent = await fs.readFile(fullPath, "utf8");
const { data, content } = matter(rawContent);
// allow *very* limited markdown to be used in post titles // allow *very* limited markdown to be used in post titles
const parseTitle = async (title: string, allowedTags: string[] = []): Promise<string> => { const parseTitle = async (title: string, allowedTags: string[] = []): Promise<string> => {
return String( return String(
@ -58,35 +49,32 @@ export const getPostData = async (
// process title as both plain and stylized // process title as both plain and stylized
const [title, htmlTitle] = await Promise.all([ const [title, htmlTitle] = await Promise.all([
parseTitle(data.title), parseTitle(frontmatter.title),
parseTitle(data.title, ["code", "em", "strong"]), parseTitle(frontmatter.title, ["code", "em", "strong"]),
]); ]);
// return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content // return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content
return { return {
frontMatter: { ...(frontmatter as Partial<FrontMatter>),
...(data as Partial<FrontMatter>), // zero markdown title:
// zero markdown title: title,
title, htmlTitle,
htmlTitle, slug,
slug, date: formatDate(frontmatter.date), // validate/normalize the date string provided from front matter
date: formatDate(data.date), // validate/normalize the date string provided from front matter permalink: `${config.baseUrl}/${POSTS_DIR}/${slug}/`,
permalink: `${config.baseUrl}/${POSTS_DIR}/${slug}/`, image: frontmatter.image ? `${config.baseUrl}${frontmatter.image}` : undefined,
image: data.image ? `${config.baseUrl}${data.image}` : undefined,
},
markdown: content,
}; };
}; };
export const getPostSlugs = pMemoize(async (): Promise<string[]> => { export const getPostSlugs = pMemoize(async (): Promise<string[]> => {
// list all .mdx files in POSTS_DIR // list all .mdx files in POSTS_DIR
const mdxFiles = await glob("*.mdx", { const mdxFiles = await glob("**/*.mdx", {
cwd: path.join(process.cwd(), POSTS_DIR), cwd: path.join(process.cwd(), POSTS_DIR),
dot: false, dot: false,
}); });
// strip the .mdx extensions from filenames // strip the .mdx extensions from filenames
const slugs = mdxFiles.map((fileName) => fileName.replace(/\.mdx$/, "")); const slugs = mdxFiles.map((fileName) => fileName.replace(/\/index\.mdx$/, ""));
return slugs; return slugs;
}); });
@ -94,7 +82,7 @@ export const getPostSlugs = pMemoize(async (): Promise<string[]> => {
// returns the parsed front matter of ALL posts, sorted reverse chronologically // returns the parsed front matter of ALL posts, sorted reverse chronologically
export const getAllPosts = pMemoize(async (): Promise<FrontMatter[]> => { export const getAllPosts = pMemoize(async (): Promise<FrontMatter[]> => {
// for each post, query its front matter // for each post, query its front matter
const data = await pMap(await getPostSlugs(), async (slug) => (await getPostData(slug)).frontMatter); const data = await pMap(await getPostSlugs(), async (slug) => await getFrontMatter(slug));
// sort the results by date // sort the results by date
data.sort((post1, post2) => (post1.date > post2.date ? -1 : 1)); data.sort((post1, post2) => (post1.date > post2.date ? -1 : 1));

View File

@ -3,7 +3,6 @@ export { default as rehypeSanitize } from "rehype-sanitize";
export { default as rehypeSlug } from "rehype-slug"; export { default as rehypeSlug } from "rehype-slug";
export { default as rehypeStringify } from "rehype-stringify"; export { default as rehypeStringify } from "rehype-stringify";
export { default as rehypeUnwrapImages } from "rehype-unwrap-images"; export { default as rehypeUnwrapImages } from "rehype-unwrap-images";
export { default as remarkFrontmatter } from "remark-frontmatter";
export { default as remarkGfm } from "remark-gfm"; export { default as remarkGfm } from "remark-gfm";
export { default as remarkParse } from "remark-parse"; export { default as remarkParse } from "remark-parse";
export { default as remarkRehype } from "remark-rehype"; export { default as remarkRehype } from "remark-rehype";

View File

@ -137,7 +137,6 @@ const withBundleAnalyzer = createBundleAnalyzer({
const withMDX = createMDX({ const withMDX = createMDX({
options: { options: {
remarkPlugins: [ remarkPlugins: [
mdxPlugins.remarkFrontmatter,
[mdxPlugins.remarkGfm, { singleTilde: false }], [mdxPlugins.remarkGfm, { singleTilde: false }],
[ [
mdxPlugins.remarkSmartypants, mdxPlugins.remarkSmartypants,

View File

@ -1,19 +1,17 @@
--- import featuredImage from "./sad-bernie.jpg";
title: 'Bernie Sanders'' Creepy "BERN" App Wants Your Data...From Your Best Friends'
date: 2019-05-08 10:31:02-0400 export const frontmatter = {
description: "The team behind Bernie's campaign has a new app named BERN. It's undoubtedly a smart move, but also a concerning one for privacy advocates." title: 'Bernie Sanders\' Creepy "BERN" App Wants Your Data...From Your Best Friends',
tags: date: "2019-05-08 10:31:02-0400",
- Privacy description:
- Data "The team behind Bernie's campaign has a new app named BERN. It's undoubtedly a smart move, but also a concerning one for privacy advocates.",
- Bernie Sanders tags: ["Privacy", "Data", "Bernie Sanders", "2020 Presidential Campaign", "Politics"],
- 2020 Presidential Campaign image: featuredImage.src,
- Politics };
image: "/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg"
---
The team behind Bernie Sanders' 2020 campaign [released a new web app](https://www.nbcnews.com/politics/2020-election/bernie-sanders-2020-campaign-unveils-app-increase-its-voter-database-n999206) last month named [BERN](https://app.berniesanders.com/). The goal of BERN is simple: to gather as much information as they can on as many voters in the United States as they can, and make their grassroots army of enthusiastic supporters do the work. It's undoubtedly a smart strategy, but also a concerning one for myself and other privacy advocates. The team behind Bernie Sanders' 2020 campaign [released a new web app](https://www.nbcnews.com/politics/2020-election/bernie-sanders-2020-campaign-unveils-app-increase-its-voter-database-n999206) last month named [BERN](https://app.berniesanders.com/). The goal of BERN is simple: to gather as much information as they can on as many voters in the United States as they can, and make their grassroots army of enthusiastic supporters do the work. It's undoubtedly a smart strategy, but also a concerning one for myself and other privacy advocates.
![Sad Bernie](../public/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg) ![Sad Bernie](./sad-bernie.jpg)
BERN has two features: one called "Friend-to-Friend" (described as "add everyone in your network") and another called "Community Canvassing" (described as "talk to people around you every day, e.g. on the bus, outside the grocery store, at a park"). Both of these involve phoning home to Sanders HQ with the following information on anybody you know or meet: BERN has two features: one called "Friend-to-Friend" (described as "add everyone in your network") and another called "Community Canvassing" (described as "talk to people around you every day, e.g. on the bus, outside the grocery store, at a park"). Both of these involve phoning home to Sanders HQ with the following information on anybody you know or meet:
@ -42,43 +40,43 @@ Here's one of the instructional videos provided internally to volunteers:
<Video <Video
src={{ src={{
webm: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.webm", webm: "/static/bernie-sanders-bern-app-data/friend-to-friend.webm",
mp4: "/static/images/notes/bernie-sanders-bern-app-data/friend-to-friend.mp4", mp4: "/static/bernie-sanders-bern-app-data/friend-to-friend.mp4",
}} }}
poster="/static/images/notes/bernie-sanders-bern-app-data/poster-friend-to-friend.png" poster="/static/bernie-sanders-bern-app-data/poster-friend-to-friend.png"
/> />
...and a few privacy-related questions about the friend-to-friend feature were answered by campaign staff in a separate closed webinar for volunteers this week: ...and a few privacy-related questions about the friend-to-friend feature were answered by campaign staff in a separate closed webinar for volunteers this week:
![Q&A 1](../public/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-1.png) ![Q&A 1](./webinar-qa-1.png)
![Q&A 2](../public/static/images/notes/bernie-sanders-bern-app-data/webinar-qa-2.png) ![Q&A 2](./webinar-qa-2.png)
Defenders of the BERN app have pointed out that the information used is already available from public voter rolls maintained independently by each state. This is true. But these public records have never been tied to a campaign's internal voter files through a tool that's wide open to the entire internet, with incentives to add valuable data that benefits one candidate. Defenders of the BERN app have pointed out that the information used is already available from public voter rolls maintained independently by each state. This is true. But these public records have never been tied to a campaign's internal voter files through a tool that's wide open to the entire internet, with incentives to add valuable data that benefits one candidate.
There were even unverified claims that [BERN was leaking voter ID numbers](https://info.idagent.com/blog/bern-app-exposes-150m-voter-records), which are the same as one's driver's license ID numbers in some states, through JSON responses in the first few days after its release. There don't be appear to be strict rate limits on calls to the API either, potentially inviting malicious actors from around the world — wink wink — to scrape personal data on tens of millions of Americans en masse. There were even unverified claims that [BERN was leaking voter ID numbers](https://info.idagent.com/blog/bern-app-exposes-150m-voter-records), which are the same as one's driver's license ID numbers in some states, through JSON responses in the first few days after its release. There don't be appear to be strict rate limits on calls to the API either, potentially inviting malicious actors from around the world — wink wink — to scrape personal data on tens of millions of Americans en masse.
![BERN's API response in Chrome DevTools](../public/static/images/notes/bernie-sanders-bern-app-data/json-response.jpg) ![BERN's API response in Chrome DevTools](./json-response.jpg)
Others have noted that web-based organizing tools like BERN have been used by campaigns at all levels since President Obama's well-oiled, futuristic machine in 2007. This is also true, and I'm a big fan of the trend they started. Others have noted that web-based organizing tools like BERN have been used by campaigns at all levels since President Obama's well-oiled, futuristic machine in 2007. This is also true, and I'm a big fan of the trend they started.
But the latter category of databases — like [NationBuilder](https://nationbuilder.com/) and, more notably, [NGP VAN's VoteBuilder](https://act.ngpvan.com/votebuilder) software based on the Obama campaign's inventions and now used by almost all Democratic campaigns across the United States — are secured and strictly guarded. Volunteer accounts need to be created and approved by paid campaign organizers and are locked down to provide the bare minimum amount of information necessary for one to canvass or phone bank a shortlist of voters. Every single click is also recorded in a [detailed log](/static/images/notes/bernie-sanders-bern-app-data/sanders-campaign-audit.pdf) down to the millisecond. (This is how [Bernie's organizers got busted](https://time.com/4155185/bernie-sanders-hillary-clinton-data/) snooping around Hillary's VoteBuilder data last cycle, by the way.) But the latter category of databases — like [NationBuilder](https://nationbuilder.com/) and, more notably, [NGP VAN's VoteBuilder](https://act.ngpvan.com/votebuilder) software based on the Obama campaign's inventions and now used by almost all Democratic campaigns across the United States — are secured and strictly guarded. Volunteer accounts need to be created and approved by paid campaign organizers and are locked down to provide the bare minimum amount of information necessary for one to canvass or phone bank a shortlist of voters. Every single click is also recorded in a [detailed log](/static/bernie-sanders-bern-app-data/sanders-campaign-audit.pdf) down to the millisecond. (This is how [Bernie's organizers got busted](https://time.com/4155185/bernie-sanders-hillary-clinton-data/) snooping around Hillary's VoteBuilder data last cycle, by the way.)
![NGP VAN's audit of the Sanders campaign's VoteBuilder ![NGP VAN's audit of the Sanders campaign's VoteBuilder
activity](../public/static/images/notes/bernie-sanders-bern-app-data/votebuilder-audit.png) activity](./votebuilder-audit.png)
BERN is taking this to an unprecedented level. Allowing anybody on the internet to sign up and add others' personal information to the campaign's database without their knowledge is troubling, especially when you consider the gamified "points" system they've added as an incentive to report as much information on as many people as possible. BERN is taking this to an unprecedented level. Allowing anybody on the internet to sign up and add others' personal information to the campaign's database without their knowledge is troubling, especially when you consider the gamified "points" system they've added as an incentive to report as much information on as many people as possible.
![BERN discussion on /r/SandersForPresident ![BERN discussion on /r/SandersForPresident
thread](../public/static/images/notes/bernie-sanders-bern-app-data/reddit-bros.png) thread](./reddit-bros.png)
In addition to the points system, it was revealed in the webinar mentioned above that the campaign is planning on giving out shiny rewards based on how many friends one adds, setting expectations at 50+ contacts to reach the "Bernie Super Bundler" tier — whatever that means. In addition to the points system, it was revealed in the webinar mentioned above that the campaign is planning on giving out shiny rewards based on how many friends one adds, setting expectations at 50+ contacts to reach the "Bernie Super Bundler" tier — whatever that means.
![Webinar Slide 1](../public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-1.png) ![Webinar Slide 1](./webinar-slide-1.png)
In the middle of the webinar, the organizer also paused the presentation for _fifteen minutes_ — complete with a countdown clock — and told volunteers to race to add as many of their friends as possible in that time. She announced afterwards that participants added 20 to 40 friends into the app on average, with some allegedly adding close to 100 in fifteen minutes. In the middle of the webinar, the organizer also paused the presentation for _fifteen minutes_ — complete with a countdown clock — and told volunteers to race to add as many of their friends as possible in that time. She announced afterwards that participants added 20 to 40 friends into the app on average, with some allegedly adding close to 100 in fifteen minutes.
![Webinar Slide 2](../public/static/images/notes/bernie-sanders-bern-app-data/webinar-slide-2.png) ![Webinar Slide 2](./webinar-slide-2.png)
The [Privacy Policy link](https://berniesanders.com/privacy-policy/) at the bottom of the app links to a generic policy that looks like it's been copied from a default Wix website. There's no mention of the BERN app, no details of how they explicitly use our information, and no sign of an opt-out procedure. The [Privacy Policy link](https://berniesanders.com/privacy-policy/) at the bottom of the app links to a generic policy that looks like it's been copied from a default Wix website. There's no mention of the BERN app, no details of how they explicitly use our information, and no sign of an opt-out procedure.

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 359 KiB

After

Width:  |  Height:  |  Size: 359 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,19 +1,17 @@
--- import featuredImage from "./archive-is.png";
title: "Does Cloudflare's 1.1.1.1 DNS Block Archive.is?"
date: 2019-05-04 09:35:12-0400 export const frontmatter = {
description: "Short answer: no. Quite the opposite, actually — Archive.is is intentionally blocking 1.1.1.1 users. Here's why." title: "Does Cloudflare's 1.1.1.1 DNS Block Archive.is?",
tags: date: "2019-05-04 09:35:12-0400",
- Cloudflare description:
- DNS "Short answer: no. Quite the opposite, actually — Archive.is is intentionally blocking 1.1.1.1 users. Here's why.",
- Networking tags: ["Cloudflare", "DNS", "Networking", "Privacy", "Temper Tantrums"],
- Privacy image: featuredImage.src,
- Temper Tantrums };
image: "/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png"
---
**tl;dr:** No. Quite the opposite, actually — [Archive.is](https://archive.is/)'s owner is intentionally blocking 1.1.1.1 users. **tl;dr:** No. Quite the opposite, actually — [Archive.is](https://archive.is/)'s owner is intentionally blocking 1.1.1.1 users.
![Archive.today screenshot](../public/static/images/notes/cloudflare-dns-archive-is-blocked/archive-is.png) ![Archive.today screenshot](./archive-is.png)
A [recent post on Hacker News](https://news.ycombinator.com/item?id=19828317) pointed out something I've noticed myself over the past year — the [Archive.is](https://archive.is/) website archiving tool (aka [Archive.today](https://archive.today/) and a few other TLDs) appears unresponsive when I'm on my home network, where I use Cloudflare's fantastic public DNS service, [1.1.1.1](https://1.1.1.1/). I didn't connect the two variables until I read this post, where somebody noticed that the Archive.is domain resolves for [Google's 8.8.8.8](https://developers.google.com/speed/public-dns/) DNS, but not 1.1.1.1. An interesting and timeless debate on [privacy versus convenience](https://www.adweek.com/digital/why-consumers-are-increasingly-willing-to-trade-privacy-for-convenience/) ensued. A [recent post on Hacker News](https://news.ycombinator.com/item?id=19828317) pointed out something I've noticed myself over the past year — the [Archive.is](https://archive.is/) website archiving tool (aka [Archive.today](https://archive.today/) and a few other TLDs) appears unresponsive when I'm on my home network, where I use Cloudflare's fantastic public DNS service, [1.1.1.1](https://1.1.1.1/). I didn't connect the two variables until I read this post, where somebody noticed that the Archive.is domain resolves for [Google's 8.8.8.8](https://developers.google.com/speed/public-dns/) DNS, but not 1.1.1.1. An interesting and timeless debate on [privacy versus convenience](https://www.adweek.com/digital/why-consumers-are-increasingly-willing-to-trade-privacy-for-convenience/) ensued.

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,18 +1,15 @@
--- import featuredImage from "./terminal.png";
title: 'Cool Bash Tricks for Your Terminal''s "Dotfiles"'
date: 2018-12-10 20:01:50-0400
description: "Bashfiles usually contain shortcuts compatible with Bash terminals to automate convoluted commands. Here's a summary of the ones I find most helpful that you can add to your own .bash_profile or .bashrc file."
tags:
- Dotfiles
- Hacks
- macOS
- Programming
- Terminal
- Tutorial
image: "/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png"
---
![Terminal.app on macOS](../public/static/images/notes/cool-bash-tricks-for-your-terminal-dotfiles/terminal.png) export const frontmatter = {
title: 'Cool Bash Tricks for Your Terminal\'s "Dotfiles"',
date: "2018-12-10 20:01:50-0400",
description:
"Bashfiles usually contain shortcuts compatible with Bash terminals to automate convoluted commands. Here's a summary of the ones I find most helpful that you can add to your own .bash_profile or .bashrc file.",
tags: ["Dotfiles", "Hacks", "macOS", "Programming", "Terminal", "Tutorial"],
image: featuredImage.src,
};
![Terminal.app on macOS](./terminal.png)
You may have noticed the recent trend of techies [posting their "dotfiles" on GitHub](https://github.com/topics/dotfiles) for the world to see. These usually contain shortcuts compatible with Bash terminals to automate convoluted commands that, I'll admit, I needed to Google every single time. You may have noticed the recent trend of techies [posting their "dotfiles" on GitHub](https://github.com/topics/dotfiles) for the world to see. These usually contain shortcuts compatible with Bash terminals to automate convoluted commands that, I'll admit, I needed to Google every single time.

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

@ -1,15 +1,13 @@
--- import featuredImage from "./covid19dashboards.png";
title: "COVID-19 vs. the Open Source Community ⚔️"
date: 2020-03-23 15:17:09-0400 export const frontmatter = {
description: "The open source community is rallying together like no other to provide coronavirus information to the public in innovative ways." title: "COVID-19 vs. the Open Source Community ⚔️",
tags: date: "2020-03-23 15:17:09-0400",
- Open Source description:
- COVID-19 "The open source community is rallying together like no other to provide coronavirus information to the public in innovative ways.",
- Coronavirus tags: ["Open Source", "COVID-19", "Coronavirus", "Public Health", "GitHub"],
- Public Health image: featuredImage.src,
- GitHub };
image: "/static/images/notes/coronavirus-open-source/covid19dashboards.png"
---
We're all quickly learning that worldwide pandemics can bring out both [the best](https://www.vox.com/culture/2020/3/13/21179293/coronavirus-italy-covid19-music-balconies-sing) and [the worst](https://twitter.com/9NewsAUS/status/1236088663093608448) of humanity. But one thing has become readily apparent to me — outside of the large teams of medical professionals risking their lives right this minute, the open source community stands alone in its ability to rapidly organize in the midst of chaos to give back to the world and, in this case, make it safer for all of us. We're all quickly learning that worldwide pandemics can bring out both [the best](https://www.vox.com/culture/2020/3/13/21179293/coronavirus-italy-covid19-music-balconies-sing) and [the worst](https://twitter.com/9NewsAUS/status/1236088663093608448) of humanity. But one thing has become readily apparent to me — outside of the large teams of medical professionals risking their lives right this minute, the open source community stands alone in its ability to rapidly organize in the midst of chaos to give back to the world and, in this case, make it safer for all of us.
@ -21,7 +19,7 @@ Now that Americans are _finally_ starting to get tested for the coronavirus, inf
The maintainers are also [fully transparent](https://covidtracking.com/about-tracker/) about their process and take great care to annotate individual figures with the methodology used to arrive at each, which has earned them the [trust](https://covidtracking.com/#press) of even the largest national news organizations reporting on COVID-19. The maintainers are also [fully transparent](https://covidtracking.com/about-tracker/) about their process and take great care to annotate individual figures with the methodology used to arrive at each, which has earned them the [trust](https://covidtracking.com/#press) of even the largest national news organizations reporting on COVID-19.
![The COVID Tracking Project](../public/static/images/notes/coronavirus-open-source/covidtracking.png) ![The COVID Tracking Project](./covidtracking.png)
## [#findthemasks](https://findthemasks.com/) <OctocatLink repo="r-pop/findthemasks" /> ## [#findthemasks](https://findthemasks.com/) <OctocatLink repo="r-pop/findthemasks" />
@ -29,7 +27,7 @@ This one might be my favorite, simply because of its laser-like focus on solving
_Please_ look up your local hospitals on [#findthemasks](https://findthemasks.com/#sites) and follow their instructions to donate anything you have hoarded — it's likely the single most impactful thing you can do at this point. If you don't see your local hospital, or don't feel comfortable shipping equipment to any hospital listed, you can also visit [PPE Link](https://ppelink.org/ppe-donations/) and they will connect you with hospitals in your area. _Please_ look up your local hospitals on [#findthemasks](https://findthemasks.com/#sites) and follow their instructions to donate anything you have hoarded — it's likely the single most impactful thing you can do at this point. If you don't see your local hospital, or don't feel comfortable shipping equipment to any hospital listed, you can also visit [PPE Link](https://ppelink.org/ppe-donations/) and they will connect you with hospitals in your area.
![#findthemasks](../public/static/images/notes/coronavirus-open-source/findthemasks.png) ![#findthemasks](./findthemasks.png)
## [#StayTheFuckHome](https://staythefuckhome.com/) <OctocatLink repo="flore2003/staythefuckhome" /> ## [#StayTheFuckHome](https://staythefuckhome.com/) <OctocatLink repo="flore2003/staythefuckhome" />
@ -37,37 +35,37 @@ I figured I'd throw in this cheeky website broadcasting a simple but serious mes
The [GitHub community](https://github.com/flore2003/staythefuckhome/pulls?q=is%3Apr) has translated the instructional essay into over a dozen different languages — including a [safe-for-work version](https://staythefuckhome.com/sfw/), if that helps — and they're [looking for more translators](https://github.com/flore2003/staythefuckhome#contributing) if you're multilingual and need something besides Netflix to fill your time with while you **_stay the fuck home!_** 😉 The [GitHub community](https://github.com/flore2003/staythefuckhome/pulls?q=is%3Apr) has translated the instructional essay into over a dozen different languages — including a [safe-for-work version](https://staythefuckhome.com/sfw/), if that helps — and they're [looking for more translators](https://github.com/flore2003/staythefuckhome#contributing) if you're multilingual and need something besides Netflix to fill your time with while you **_stay the fuck home!_** 😉
![#StayTheFuckHome](../public/static/images/notes/coronavirus-open-source/staythefuckhome.png) ![#StayTheFuckHome](./staythefuckhome.png)
## [COVID-19 Dashboards](https://covid19dashboards.com/) <OctocatLink repo="github/covid19-dashboard" /> ## [COVID-19 Dashboards](https://covid19dashboards.com/) <OctocatLink repo="github/covid19-dashboard" />
This collection of various visualizations is fascinating (and sobering) to look at. If you're smarter than I am and have experience in data analysis, their team (led by a [GitHub engineer](https://github.com/hamelsmu)) would be more than happy to [add your contribution](https://github.com/github/covid19-dashboard/blob/master/CONTRIBUTING.md) to the site — they're using [Jupyter Notebooks](https://jupyter.org/) and [fastpages](https://github.com/fastai/fastpages). This collection of various visualizations is fascinating (and sobering) to look at. If you're smarter than I am and have experience in data analysis, their team (led by a [GitHub engineer](https://github.com/hamelsmu)) would be more than happy to [add your contribution](https://github.com/github/covid19-dashboard/blob/master/CONTRIBUTING.md) to the site — they're using [Jupyter Notebooks](https://jupyter.org/) and [fastpages](https://github.com/fastai/fastpages).
![COVID-19 Dashboards](../public/static/images/notes/coronavirus-open-source/covid19dashboards.png) ![COVID-19 Dashboards](./covid19dashboards.png)
## [CoronaTracker](https://coronatracker.samabox.com/) <OctocatLink repo="MhdHejazi/CoronaTracker" /> ## [CoronaTracker](https://coronatracker.samabox.com/) <OctocatLink repo="MhdHejazi/CoronaTracker" />
CoronaTracker is a _beautiful_ cross-platform app for iOS and macOS with intuitive maps and charts fed by reputable live data. Apple is [being justifiably picky](https://developer.apple.com/news/?id=03142020a) about "non-official" Coronavirus apps in their App Store ([so is Google](https://blog.google/inside-google/company-announcements/coronavirus-covid19-response/), by the way) but you can still [download the macOS app directly](https://coronatracker.samabox.com/) or [compile the iOS source code](https://github.com/MhdHejazi/CoronaTracker#1-ios-app) yourself using Xcode if you wish. CoronaTracker is a _beautiful_ cross-platform app for iOS and macOS with intuitive maps and charts fed by reputable live data. Apple is [being justifiably picky](https://developer.apple.com/news/?id=03142020a) about "non-official" Coronavirus apps in their App Store ([so is Google](https://blog.google/inside-google/company-announcements/coronavirus-covid19-response/), by the way) but you can still [download the macOS app directly](https://coronatracker.samabox.com/) or [compile the iOS source code](https://github.com/MhdHejazi/CoronaTracker#1-ios-app) yourself using Xcode if you wish.
![CoronaTracker](../public/static/images/notes/coronavirus-open-source/coronatracker.png) ![CoronaTracker](./coronatracker.png)
## [Staying Home Club](https://stayinghome.club/) <OctocatLink repo="phildini/stayinghomeclub" /> ## [Staying Home Club](https://stayinghome.club/) <OctocatLink repo="phildini/stayinghomeclub" />
A bit more family-friendly than [#StayTheFuckHome](https://staythefuckhome.com/), the [Staying Home Club](https://stayinghome.club/) is maintaining a running list of over a thousand companies and universities mandating that employees and students work from home, as well as events that have been canceled or moved online. Quarantining yourself might feel lonely, but here's solid proof that you're far from alone right now. A bit more family-friendly than [#StayTheFuckHome](https://staythefuckhome.com/), the [Staying Home Club](https://stayinghome.club/) is maintaining a running list of over a thousand companies and universities mandating that employees and students work from home, as well as events that have been canceled or moved online. Quarantining yourself might feel lonely, but here's solid proof that you're far from alone right now.
![Staying Home Club](../public/static/images/notes/coronavirus-open-source/stayinghome.png) ![Staying Home Club](./stayinghome.png)
## [Nextstrain for nCoV](https://nextstrain.org/ncov) <OctocatLink repo="nextstrain/ncov" /> ## [Nextstrain for nCoV](https://nextstrain.org/ncov) <OctocatLink repo="nextstrain/ncov" />
This one is a bit over my head, but apparently [Nextstrain](https://nextstrain.org/) is a pretty impressive open-source service targeted at genome data analysis and visualization of different pathogens. Their [COVID-19 page](https://nextstrain.org/ncov) is still awe-inspiring to look at for a layman like me, but probably a thousand times more so if you're an actual scientist — in which case, the [genome data they've open-sourced](https://github.com/nextstrain/ncov) might be of interest to you. This one is a bit over my head, but apparently [Nextstrain](https://nextstrain.org/) is a pretty impressive open-source service targeted at genome data analysis and visualization of different pathogens. Their [COVID-19 page](https://nextstrain.org/ncov) is still awe-inspiring to look at for a layman like me, but probably a thousand times more so if you're an actual scientist — in which case, the [genome data they've open-sourced](https://github.com/nextstrain/ncov) might be of interest to you.
![Nextstrain for nCOV](../public/static/images/notes/coronavirus-open-source/nextstrain.png) ![Nextstrain for nCOV](./nextstrain.png)
## [Johns Hopkins 2019-nCoV Data](https://systems.jhu.edu/research/public-health/ncov/) <OctocatLink repo="CSSEGISandData/COVID-19" /> ## [Johns Hopkins 2019-nCoV Data](https://systems.jhu.edu/research/public-health/ncov/) <OctocatLink repo="CSSEGISandData/COVID-19" />
Johns Hopkins University's [visual COVID-19 global dashboard](https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6) has been bookmarked as my go-to source of information since the beginning of this crisis earlier this year. Now, JHU's [Center for Systems Science and Engineering](https://systems.jhu.edu/) has open-sourced [their data and analysis](https://github.com/CSSEGISandData/COVID-19) for anybody to use. Johns Hopkins University's [visual COVID-19 global dashboard](https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6) has been bookmarked as my go-to source of information since the beginning of this crisis earlier this year. Now, JHU's [Center for Systems Science and Engineering](https://systems.jhu.edu/) has open-sourced [their data and analysis](https://github.com/CSSEGISandData/COVID-19) for anybody to use.
![Johns Hopkins 2019-nCoV Data](../public/static/images/notes/coronavirus-open-source/hopkins.png) ![Johns Hopkins 2019-nCoV Data](./hopkins.png)
## [COVID-19 Scenarios](https://neherlab.org/covid19/) <OctocatLink repo="neherlab/covid19_scenarios" /> ## [COVID-19 Scenarios](https://neherlab.org/covid19/) <OctocatLink repo="neherlab/covid19_scenarios" />
@ -75,13 +73,13 @@ COVID-19 Scenarios will probably hit everyone in a different way, depending on y
The maintainers at the [Neher Lab in Basel, Switzerland](https://neherlab.org/) even have a [discussion thread](https://github.com/neherlab/covid19_scenarios/issues/18) and an [open chatroom](https://spectrum.chat/covid19-scenarios/general/questions-discussions~8d49f461-a890-4beb-84f7-2d6ed0ae503a) set up for both scientists and non-scientists to ask questions and post ideas, which I find really nice of them! The maintainers at the [Neher Lab in Basel, Switzerland](https://neherlab.org/) even have a [discussion thread](https://github.com/neherlab/covid19_scenarios/issues/18) and an [open chatroom](https://spectrum.chat/covid19-scenarios/general/questions-discussions~8d49f461-a890-4beb-84f7-2d6ed0ae503a) set up for both scientists and non-scientists to ask questions and post ideas, which I find really nice of them!
![COVID-19 Scenarios](../public/static/images/notes/coronavirus-open-source/scenarios.png) ![COVID-19 Scenarios](./scenarios.png)
## [Corona Data Scraper](https://coronadatascraper.com/#home) <OctocatLink repo="lazd/coronadatascraper" /> ## [Corona Data Scraper](https://coronadatascraper.com/#home) <OctocatLink repo="lazd/coronadatascraper" />
Similar to the [COVID Tracking Project](https://covidtracking.com/) above, the [Corona Data Scraper](https://coronadatascraper.com/#home) has set up an automated process to scrape verified data from across the web to form massive CSV spreadsheets and JSON objects. They even [rate the quality](https://github.com/lazd/coronadatascraper#source-rating) of each source to prioritize data accordingly. Similar to the [COVID Tracking Project](https://covidtracking.com/) above, the [Corona Data Scraper](https://coronadatascraper.com/#home) has set up an automated process to scrape verified data from across the web to form massive CSV spreadsheets and JSON objects. They even [rate the quality](https://github.com/lazd/coronadatascraper#source-rating) of each source to prioritize data accordingly.
![Corona Data Scraper](../public/static/images/notes/coronavirus-open-source/coronadatascraper.png) ![Corona Data Scraper](./coronadatascraper.png)
## [Folding@home](https://foldingathome.org/covid19/) <OctocatLink repo="FoldingAtHome/coronavirus" /> ## [Folding@home](https://foldingathome.org/covid19/) <OctocatLink repo="FoldingAtHome/coronavirus" />
@ -93,10 +91,10 @@ You can [download their software here](https://foldingathome.org/start-folding/)
<Video <Video
src={{ src={{
webm: "/static/images/notes/coronavirus-open-source/folding.webm", webm: "/static/coronavirus-open-source/folding.webm",
mp4: "/static/images/notes/coronavirus-open-source/folding.mp4", mp4: "/static/coronavirus-open-source/folding.mp4",
}} }}
poster="/static/images/notes/coronavirus-open-source/folding-thumb.png" poster="/static/coronavirus-open-source/folding-thumb.png"
autoplay autoplay
/> />
@ -104,6 +102,6 @@ You can [download their software here](https://foldingathome.org/start-folding/)
To wrap this list up, I thought I'd include [yet another API](https://github.com/ExpDev07/coronavirus-tracker-api) fed by multiple data sources that you can use to create your own open-source project if any of these inspired you. This one is incredibly flexible in terms of [query parameters and endpoints](https://github.com/ExpDev07/coronavirus-tracker-api#api-endpoints) but they all return simple JSON responses like we all know and love. To wrap this list up, I thought I'd include [yet another API](https://github.com/ExpDev07/coronavirus-tracker-api) fed by multiple data sources that you can use to create your own open-source project if any of these inspired you. This one is incredibly flexible in terms of [query parameters and endpoints](https://github.com/ExpDev07/coronavirus-tracker-api#api-endpoints) but they all return simple JSON responses like we all know and love.
![Coronavirus Tracker API](../public/static/images/notes/coronavirus-open-source/tracker-api.png) ![Coronavirus Tracker API](./tracker-api.png)
### Stay safe (and [home](https://staythefuckhome.com/ "One last time...")), friends! ❤️ ### Stay safe (and [home](https://staythefuckhome.com/ "One last time...")), friends! ❤️

View File

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 295 KiB

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -1,15 +1,12 @@
--- import featuredImage from "./codepen.png";
title: "Animated Waving Hand Emoji 👋 Using CSS"
date: 2019-04-17 14:20:10-0400 export const frontmatter = {
description: "How to make the 👋 waving hand emoji actually wave using pure CSS animation!" title: "Animated Waving Hand Emoji 👋 Using CSS",
tags: date: "2019-04-17 14:20:10-0400",
- CSS description: "How to make the 👋 waving hand emoji actually wave using pure CSS animation!",
- Animation tags: ["CSS", "Animation", "Emoji", "Keyframes", "Cool Tricks"],
- Emoji image: featuredImage.src,
- Keyframes };
- Cool Tricks
image: "/static/images/notes/css-waving-hand-emoji/codepen.png"
---
## Howdy, friends! 👋 ## Howdy, friends! 👋

View File

@ -1,15 +1,9 @@
--- export const frontmatter = {
title: "How To: Add Dark Mode to a Website 🌓" title: "How To: Add Dark Mode to a Website 🌓",
date: 2021-10-15 08:56:33-0400 date: "2021-10-15 08:56:33-0400",
description: "Simple dark mode switching using local storage, OS preference detection, and minimal JavaScript." description: "Simple dark mode switching using local storage, OS preference detection, and minimal JavaScript.",
tags: tags: ["JavaScript", "NPM", "CSS", "Dark Mode", "How To", "Tutorial"],
- JavaScript };
- NPM
- CSS
- Dark Mode
- How To
- Tutorial
---
Love it or hate it, it seems that the [dark mode fad](https://en.wikipedia.org/wiki/Light-on-dark_color_scheme) is here to stay, especially now that more and more devices have [OLED screens](https://www.macrumors.com/2019/10/21/ios-13-dark-mode-extends-iphone-battery-life/) that display true blacks... which means that these trendsetters might go blind from your site's insanely white background if you're behind the curve and don't offer your own dark mode. Love it or hate it, it seems that the [dark mode fad](https://en.wikipedia.org/wiki/Light-on-dark_color_scheme) is here to stay, especially now that more and more devices have [OLED screens](https://www.macrumors.com/2019/10/21/ios-13-dark-mode-extends-iphone-battery-life/) that display true blacks... which means that these trendsetters might go blind from your site's insanely white background if you're behind the curve and don't offer your own dark mode.

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 403 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -1,19 +1,17 @@
--- import featuredImage from "./email.png";
title: "Why I'm Dropping Dropbox"
date: 2019-11-20 17:22:43-0400 export const frontmatter = {
description: "I'm finally canceling my Dropbox Pro account and moving to iCloud Drive for synchronized cloud storage." title: "Why I'm Dropping Dropbox",
tags: date: "2019-11-20 17:22:43-0400",
- Cloud Storage description:
- Dropbox "I'm finally canceling my Dropbox Pro account and moving to iCloud Drive for synchronized cloud storage.",
- Apple tags: ["Cloud Storage", "Dropbox", "Apple", "iCloud Drive", "Betrayal"],
- iCloud Drive image: featuredImage.src,
- Betrayal };
image: "/static/images/notes/dropping-dropbox/email.png"
---
I've been a loyal Dropbox user since its inception as a [Y Combinator startup](https://www.ycombinator.com/apply/dropbox/) ten years ago. Having a folder on all of my devices that instantly synchronized with each other was a game-changer for me, and I grew dependent on it more and more as they gave out free storage like candy — 48 GB for having a Samsung Chromebook, 1 GB for "Posting \<3 to Twitter," and so on — until I needed to upgrade to Dropbox Pro. But this month I canceled my Pro subscription after a few too many strikes. I've been a loyal Dropbox user since its inception as a [Y Combinator startup](https://www.ycombinator.com/apply/dropbox/) ten years ago. Having a folder on all of my devices that instantly synchronized with each other was a game-changer for me, and I grew dependent on it more and more as they gave out free storage like candy — 48 GB for having a Samsung Chromebook, 1 GB for "Posting \<3 to Twitter," and so on — until I needed to upgrade to Dropbox Pro. But this month I canceled my Pro subscription after a few too many strikes.
![Deleting 401,907 files from Dropbox... 😬](../public/static/images/notes/dropping-dropbox/email.png) ![Deleting 401,907 files from Dropbox... 😬](./email.png)
## Five strikes, you're out... ## Five strikes, you're out...
@ -30,21 +28,21 @@ Decisions made by the top folks at Dropbox gave me an increasingly sour taste in
<Video <Video
src={{ src={{
webm: "/static/images/notes/dropping-dropbox/cancel.webm", webm: "/static/dropping-dropbox/cancel.webm",
mp4: "/static/images/notes/dropping-dropbox/cancel.mp4", mp4: "/static/dropping-dropbox/cancel.mp4",
}} }}
poster="/static/images/notes/dropping-dropbox/cancel.png" poster="/static/dropping-dropbox/cancel.png"
/> />
## Seeking an alternative... ## Seeking an alternative...
The infamous [Apple Ecosystem™](https://medium.com/swlh/the-irresistible-lure-of-the-apple-ecosystem-81bf8d66294a) has held me firmly in its grasp for over a decade now, and the main requirement of a replacement cloud storage service for me was smooth interoperability between my MacBook, iPhone, and iPad. The infamous [Apple Ecosystem™](https://medium.com/swlh/the-irresistible-lure-of-the-apple-ecosystem-81bf8d66294a) has held me firmly in its grasp for over a decade now, and the main requirement of a replacement cloud storage service for me was smooth interoperability between my MacBook, iPhone, and iPad.
![iCloud Drive storage](../public/static/images/notes/dropping-dropbox/icloud-storage.png) ![iCloud Drive storage](./icloud-storage.png)
I've never been a proponent of leaving all your eggs in one basket. But it's hard to ignore the convenience of Apple's streamlined (and [finally](https://www.imore.com/developers-encounter-major-icloud-issues-ios-13-beta) reliable) [**iCloud Drive**](https://www.apple.com/icloud/), which is already installed on all of my devices (and actually cheaper than Dropbox gigabyte-for-gigabyte, at \$9.99/month for 2 TB). In fact, it's nearly invisible on macOS: I can simply save files in my Documents or Desktop folders as I always have and they're uploaded in the background. Git repositories now sync just fine and my files reappeared without a hitch after I recently formatted my Mac. I've never been a proponent of leaving all your eggs in one basket. But it's hard to ignore the convenience of Apple's streamlined (and [finally](https://www.imore.com/developers-encounter-major-icloud-issues-ios-13-beta) reliable) [**iCloud Drive**](https://www.apple.com/icloud/), which is already installed on all of my devices (and actually cheaper than Dropbox gigabyte-for-gigabyte, at \$9.99/month for 2 TB). In fact, it's nearly invisible on macOS: I can simply save files in my Documents or Desktop folders as I always have and they're uploaded in the background. Git repositories now sync just fine and my files reappeared without a hitch after I recently formatted my Mac.
![iCloud Drive](../public/static/images/notes/dropping-dropbox/icloud-drive.png) ![iCloud Drive](./icloud-drive.png)
I still use (and highly recommend) [**Backblaze**](https://www.backblaze.com/) ([referral link](https://secure.backblaze.com/r/00x84e)) to backup my home folder and add a second layer of redundancy to storing all of my most important files on ["someone else's computer."](https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/) And as long as I remember to plug in my external SSD every so often, they're also backed up locally via [Time Machine](https://support.apple.com/en-us/HT201250). I still use (and highly recommend) [**Backblaze**](https://www.backblaze.com/) ([referral link](https://secure.backblaze.com/r/00x84e)) to backup my home folder and add a second layer of redundancy to storing all of my most important files on ["someone else's computer."](https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/) And as long as I remember to plug in my external SSD every so often, they're also backed up locally via [Time Machine](https://support.apple.com/en-us/HT201250).

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -1,22 +1,20 @@
--- import featuredImage from "./hackerone-2.png";
title: "Finding Candidates for Subdomain Takeovers"
date: 2019-03-10 11:19:48-0400 export const frontmatter = {
description: "A subdomain takeover occurs when a subdomain points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself." title: "Finding Candidates for Subdomain Takeovers",
tags: date: "2019-03-10 11:19:48-0400",
- Pentesting description:
- Infosec "A subdomain takeover occurs when a subdomain points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself.",
- Subdomain Takeover tags: ["Pentesting", "Infosec", "Subdomain Takeover", "Bug Bounty", "Tutorial"],
- Bug Bounty image: featuredImage.src,
- Tutorial };
image: "/static/images/notes/finding-candidates-subdomain-takeovers/hackerone-2.png"
---
A **subdomain takeover** occurs when a subdomain (like _example_.jarv.is) points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself. A **subdomain takeover** occurs when a subdomain (like _example_.jarv.is) points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself.
Not only are takeovers a fun way to dip your toes into [penetration testing](https://www.cloudflare.com/learning/security/glossary/what-is-penetration-testing/), but they can also be incredibly lucrative thanks to [bug bounty programs](https://en.wikipedia.org/wiki/Bug_bounty_program) on services like [HackerOne](https://hackerone.com/hacktivity?order_direction=DESC&order_field=popular&filter=type%3Aall&querystring=subdomain%20takeover) and [Bugcrowd](https://bugcrowd.com/programs), where corporations pay pentesters for their discoveries. Not only are takeovers a fun way to dip your toes into [penetration testing](https://www.cloudflare.com/learning/security/glossary/what-is-penetration-testing/), but they can also be incredibly lucrative thanks to [bug bounty programs](https://en.wikipedia.org/wiki/Bug_bounty_program) on services like [HackerOne](https://hackerone.com/hacktivity?order_direction=DESC&order_field=popular&filter=type%3Aall&querystring=subdomain%20takeover) and [Bugcrowd](https://bugcrowd.com/programs), where corporations pay pentesters for their discoveries.
![Huge rewards for subdomain takeovers on ![Huge rewards for subdomain takeovers on
HackerOne!](../public/static/images/notes/finding-candidates-subdomain-takeovers/hackerone-2.png) HackerOne!](./hackerone-2.png)
For a deep dive on the implications of takeovers, which can be a pretty serious vector of attack for malicious actors to obtain information from users of the targeted company, [Patrik Hudak](https://twitter.com/0xpatrik) wrote a [great post here](https://0xpatrik.com/subdomain-takeover/). Definitely take some time to skim through it and come back here when you're ready to hunt for a potential takeover yourself. For a deep dive on the implications of takeovers, which can be a pretty serious vector of attack for malicious actors to obtain information from users of the targeted company, [Patrik Hudak](https://twitter.com/0xpatrik) wrote a [great post here](https://0xpatrik.com/subdomain-takeover/). Definitely take some time to skim through it and come back here when you're ready to hunt for a potential takeover yourself.

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -1,23 +1,20 @@
--- import featuredImage from "./actions-flow.png";
title: "I ❤️ GitHub Actions"
date: 2019-10-25 13:58:39-0400
description: "I've found a new hobby of making cool GitHub Actions, the latest tool in the CI world. Here's why."
tags:
- DevOps
- GitHub
- Continuous Integration
- Docker
- Open Source
image: "/static/images/notes/github-actions/actions-flow.png"
---
![Example workflow for a GitHub Action](../public/static/images/notes/github-actions/actions-flow.png) export const frontmatter = {
title: "I ❤️ GitHub Actions",
date: "2019-10-25 13:58:39-0400",
description: "I've found a new hobby of making cool GitHub Actions, the latest tool in the CI world. Here's why.",
tags: ["DevOps", "GitHub", "Continuous Integration", "Docker", "Open Source"],
image: featuredImage.src,
};
![Example workflow for a GitHub Action](./actions-flow.png)
Since being accepted into the beta for [GitHub Actions](https://github.com/features/actions) a few months ago, I've found a new side hobby of whipping up new (and ideally creative) actions for anybody to add to their CI pipeline. Actions are modular steps that interact with a GitHub repository and can be coded with [Docker](https://github.com/actions/hello-world-docker-action) or [JavaScript/Node](https://github.com/actions/hello-world-javascript-action) — and either way, they can be as [simple](https://github.com/jakejarvis/wait-action) or as [complex](https://github.com/jakejarvis/lighthouse-action) as you want. But in both cases, they're incredibly fun to make and the results always scratch my itch for instant gratification. Since being accepted into the beta for [GitHub Actions](https://github.com/features/actions) a few months ago, I've found a new side hobby of whipping up new (and ideally creative) actions for anybody to add to their CI pipeline. Actions are modular steps that interact with a GitHub repository and can be coded with [Docker](https://github.com/actions/hello-world-docker-action) or [JavaScript/Node](https://github.com/actions/hello-world-javascript-action) — and either way, they can be as [simple](https://github.com/jakejarvis/wait-action) or as [complex](https://github.com/jakejarvis/lighthouse-action) as you want. But in both cases, they're incredibly fun to make and the results always scratch my itch for instant gratification.
My favorite so far is my [Lighthouse Audit action](https://github.com/jakejarvis/lighthouse-action), which spins up a headless Google Chrome instance in an Ubuntu container and runs [Google's Lighthouse tool](https://developers.google.com/web/tools/lighthouse), which scores webpages on performance, accessibility, SEO, etc. and provides actual suggestions to improve them. It's a perfect example of the power of combining containers with Git workflows. My favorite so far is my [Lighthouse Audit action](https://github.com/jakejarvis/lighthouse-action), which spins up a headless Google Chrome instance in an Ubuntu container and runs [Google's Lighthouse tool](https://developers.google.com/web/tools/lighthouse), which scores webpages on performance, accessibility, SEO, etc. and provides actual suggestions to improve them. It's a perfect example of the power of combining containers with Git workflows.
![The results of a Lighthouse audit on this website, after running tests in a headless Google Chrome.](../public/static/images/notes/github-actions/lighthouse-output.png) ![The results of a Lighthouse audit on this website, after running tests in a headless Google Chrome.](./lighthouse-output.png)
It's also been a fantastic avenue to dip my feet into the collaborative nature of GitHub and the open-source community. I've made some small apps in the past but these are the first projects where I'm regularly receiving new issues to help out with and impressive pull requests to merge. It's a great feeling! It's also been a fantastic avenue to dip my feet into the collaborative nature of GitHub and the open-source community. I've made some small apps in the past but these are the first projects where I'm regularly receiving new issues to help out with and impressive pull requests to merge. It's a great feeling!
@ -49,7 +46,7 @@ Using an action is also surprisingly simple, and more intuitive than [Travis CI]
For a more complex example, when I forked [Hugo](https://github.com/gohugoio/hugo) (the static site generator used to build this website) to make some small personalized changes, I also translated [their `.travis.yml` file](https://github.com/gohugoio/hugo/blob/master/.travis.yml) into a [`workflow.yml` file](https://github.com/jakejarvis/hugo-custom/blob/master/.github/workflows/workflow.yml) for practice, which simultaneously runs comprehensive unit tests on **three operating systems** (Ubuntu 18.04, Windows 10, and macOS 10.14) with the latest two Go versions _each!_ If the tests are all successful, it builds a Docker image and pushes it to both [Docker Hub](https://hub.docker.com/r/jakejarvis/hugo-custom) and the [GitHub Package Registry](https://github.com/jakejarvis/hugo-custom/packages) (also [in beta](https://github.com/features/package-registry)). For a more complex example, when I forked [Hugo](https://github.com/gohugoio/hugo) (the static site generator used to build this website) to make some small personalized changes, I also translated [their `.travis.yml` file](https://github.com/gohugoio/hugo/blob/master/.travis.yml) into a [`workflow.yml` file](https://github.com/jakejarvis/hugo-custom/blob/master/.github/workflows/workflow.yml) for practice, which simultaneously runs comprehensive unit tests on **three operating systems** (Ubuntu 18.04, Windows 10, and macOS 10.14) with the latest two Go versions _each!_ If the tests are all successful, it builds a Docker image and pushes it to both [Docker Hub](https://hub.docker.com/r/jakejarvis/hugo-custom) and the [GitHub Package Registry](https://github.com/jakejarvis/hugo-custom/packages) (also [in beta](https://github.com/features/package-registry)).
![Build logs for my Hugo fork](../public/static/images/notes/github-actions/hugo-logs.png) ![Build logs for my Hugo fork](./hugo-logs.png)
Then another workflow, which [lives in this website's repository](https://github.com/jakejarvis/jarv.is/blob/master/.github/workflows/gh-pages.yml), pulls that Docker image, builds the Hugo site, and pushes it to GitHub Pages. All astoundingly fast. All for free. Then another workflow, which [lives in this website's repository](https://github.com/jakejarvis/jarv.is/blob/master/.github/workflows/gh-pages.yml), pulls that Docker image, builds the Hugo site, and pushes it to GitHub Pages. All astoundingly fast. All for free.

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,18 +1,15 @@
--- import featuredImage from "./blm-topic.png";
title: "How To: Safely Rename `master` Branch on GitHub ✊🏾"
date: 2020-06-28 09:28:52-0400
description: 'Some of the most popular open-source projects are renaming their default branch from "master" on GitHub. Here''s how to do so, and safely.'
tags:
- How To
- Tutorial
- Git
- GitHub
- Open Source
- Black Lives Matter
image: "/static/images/notes/github-rename-master/github-default.png"
---
![Black lives matter.](../public/static/images/notes/github-rename-master/blm-topic.png) export const frontmatter = {
title: "How To: Safely Rename `master` Branch on GitHub ✊🏾",
date: "2020-06-28 09:28:52-0400",
description:
'Some of the most popular open-source projects are renaming their default branch from "master" on GitHub. Here\'s how to do so, and safely.',
tags: ["How To", "Tutorial", "Git", "GitHub", "Open Source", "Black Lives Matter"],
image: featuredImage.src,
};
![Black lives matter.](./blm-topic.png)
In the midst of this year's long-overdue support of the [**Black Lives Matter**](https://blacklivesmatters.carrd.co/) movement and calls to action in the US and around the world, a [new spotlight](https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html) has been placed on unchecked invocations of racially charged language in the computer science world, no matter how big or small — like the long-standing and, until recently, widely accepted terms ["master" and "slave"](https://tools.ietf.org/id/draft-knodel-terminology-00.html#master-slave) as an oppressive metaphor for ownership/importance. In the midst of this year's long-overdue support of the [**Black Lives Matter**](https://blacklivesmatters.carrd.co/) movement and calls to action in the US and around the world, a [new spotlight](https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html) has been placed on unchecked invocations of racially charged language in the computer science world, no matter how big or small — like the long-standing and, until recently, widely accepted terms ["master" and "slave"](https://tools.ietf.org/id/draft-knodel-terminology-00.html#master-slave) as an oppressive metaphor for ownership/importance.
@ -53,7 +50,7 @@ You can verify this worked by running `git branch -r`. You should see something
Setting the default branch remotely is the only step that can't be done on the command line (although you can technically [use the GitHub API](https://github.com/erbridge/github-branch-renamer)). Head to **Settings → Branches** on GitHub to [change the default branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request). Setting the default branch remotely is the only step that can't be done on the command line (although you can technically [use the GitHub API](https://github.com/erbridge/github-branch-renamer)). Head to **Settings → Branches** on GitHub to [change the default branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request).
![Changing the default branch of a GitHub repository](../public/static/images/notes/github-rename-master/github-default.png) ![Changing the default branch of a GitHub repository](./github-default.png)
### 4. Delete the old `master` branch on GitHub: ### 4. Delete the old `master` branch on GitHub:
@ -71,7 +68,7 @@ Do a quick search of your codebase for `master` to manually replace any dead ref
Pay attention to CI files — `.travis.yml`, `.github/workflows/`, `.circleci/config.yml`, etc. — and make sure there aren't any external services relying on `master` being there. For example, I almost forgot to change the branch [Netlify triggers auto-deploys](https://docs.netlify.com/site-deploys/overview/#branches-and-deploys) from to build this site: Pay attention to CI files — `.travis.yml`, `.github/workflows/`, `.circleci/config.yml`, etc. — and make sure there aren't any external services relying on `master` being there. For example, I almost forgot to change the branch [Netlify triggers auto-deploys](https://docs.netlify.com/site-deploys/overview/#branches-and-deploys) from to build this site:
![Netlify auto-deployment branch setting](../public/static/images/notes/github-rename-master/netlify-deploy.png) ![Netlify auto-deployment branch setting](./netlify-deploy.png)
~~Unfortunately, GitHub won't redirect links containing `master` to the new branch (as of now), so look for any [github.com](https://github.com/) URLs as well.~~ ~~Unfortunately, GitHub won't redirect links containing `master` to the new branch (as of now), so look for any [github.com](https://github.com/) URLs as well.~~

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 471 KiB

After

Width:  |  Height:  |  Size: 471 KiB

View File

@ -1,18 +1,15 @@
--- import featuredImage from "./apocalypse.png";
title: "How To: Automatically Backup a Linux VPS to a Separate Cloud Storage Service"
date: 2019-06-09 19:03:10-0400
description: "A walkthrough for backing up a Linux server to an external storage provider like Amazon S3 automatically."
tags:
- How To
- Tutorial
- Servers
- Backups
- Linux
- Restic
image: "/static/images/notes/how-to-backup-linux-server/apocalypse.png"
---
![The Cloud-pocalypse: Coming soon(er than you think) to a server near you.](../public/static/images/notes/how-to-backup-linux-server/apocalypse.png) export const frontmatter = {
title: "How To: Automatically Backup a Linux VPS to a Separate Cloud Storage Service",
date: "2019-06-09 19:03:10-0400",
description:
"A walkthrough for backing up a Linux server to an external storage provider like Amazon S3 automatically.",
tags: ["How To", "Tutorial", "Servers", "Backups", "Linux", "Restic"],
image: featuredImage.src,
};
![The Cloud-pocalypse: Coming soon(er than you think) to a server near you.](./apocalypse.png)
Last month, the founder of [a small startup](https://raisup.com/) got quite a bit of [attention on Twitter](https://twitter.com/w3Nicolas/status/1134529316904153089) (and [Hacker News](https://news.ycombinator.com/item?id=20064169)) when he called out [DigitalOcean](https://www.digitalocean.com/) who, in his words, "killed" his company. Long story short: DigitalOcean's automated abuse system flagged the startup's account after they spun up about ten powerful droplets for some CPU-intensive jobs and deleted them shortly after — which is literally **the biggest selling point** of a "servers by the hour" company like DigitalOcean, by the way — and, after replying to the support ticket, an unsympathetic customer support agent [declined to reactivate](https://twitter.com/w3Nicolas/status/1134529372172509184) the account without explanation. [Nicolas](https://twitter.com/w3Nicolas) had no way of even accessing his data, turning the inconvenient but trivial task of migrating servers into a potentially fatal situation for his company. Last month, the founder of [a small startup](https://raisup.com/) got quite a bit of [attention on Twitter](https://twitter.com/w3Nicolas/status/1134529316904153089) (and [Hacker News](https://news.ycombinator.com/item?id=20064169)) when he called out [DigitalOcean](https://www.digitalocean.com/) who, in his words, "killed" his company. Long story short: DigitalOcean's automated abuse system flagged the startup's account after they spun up about ten powerful droplets for some CPU-intensive jobs and deleted them shortly after — which is literally **the biggest selling point** of a "servers by the hour" company like DigitalOcean, by the way — and, after replying to the support ticket, an unsympathetic customer support agent [declined to reactivate](https://twitter.com/w3Nicolas/status/1134529372172509184) the account without explanation. [Nicolas](https://twitter.com/w3Nicolas) had no way of even accessing his data, turning the inconvenient but trivial task of migrating servers into a potentially fatal situation for his company.

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,16 +1,13 @@
--- import featuredImage from "./step7-2.png";
title: "How To: Fork a GitHub Repository & Submit a Pull Request"
date: 2019-04-09 02:17:03-0400 export const frontmatter = {
description: "Walkthrough of forking a GitHub repository, cloning it, committing your changes to a new branch, and pushing it back upstream." title: "How To: Fork a GitHub Repository & Submit a Pull Request",
tags: date: "2019-04-09 02:17:03-0400",
- How To description:
- Tutorial "Walkthrough of forking a GitHub repository, cloning it, committing your changes to a new branch, and pushing it back upstream.",
- Git tags: ["How To", "Tutorial", "Git", "Pull Request", "Open Source", "GitHub"],
- Pull Request image: featuredImage.src,
- Open Source };
- GitHub
image: "/static/images/notes/how-to-pull-request-fork-github/step7-2.png"
---
<svg width="150" height="150" viewBox="0 0 40 40" style={{ float: "right", marginBottom: "6px", marginLeft: "12px" }}> <svg width="150" height="150" viewBox="0 0 40 40" style={{ float: "right", marginBottom: "6px", marginLeft: "12px" }}>
<path d="M6.5 35v-4.8c0-5.4 4.3-9.7 9.7-9.7h7.6c5.4 0 9.7-4.3 9.7-9.7V6M6.5 32.5v-26" fill="none" stroke="#a3b7cc" /> <path d="M6.5 35v-4.8c0-5.4 4.3-9.7 9.7-9.7h7.6c5.4 0 9.7-4.3 9.7-9.7V6M6.5 32.5v-26" fill="none" stroke="#a3b7cc" />
@ -34,7 +31,7 @@ Starting from the very beginning, we'll fork an existing repository to our accou
Assuming you're using GitHub, this step is easy. Just find the repository you're contributing to and press the Fork button in the upper right. This will create an exact copy of the repository (and all of its branches) under your own username. Assuming you're using GitHub, this step is easy. Just find the repository you're contributing to and press the Fork button in the upper right. This will create an exact copy of the repository (and all of its branches) under your own username.
![Step 1](../public/static/images/notes/how-to-pull-request-fork-github/step1.png) ![Step 1](./step1.png)
## 2. Clone your new fork locally ## 2. Clone your new fork locally
@ -44,7 +41,7 @@ GitHub will automatically redirect you to the forked repository under your usern
git clone git@github.com:jakejarvis/react-native.git git clone git@github.com:jakejarvis/react-native.git
``` ```
![Step 2](../public/static/images/notes/how-to-pull-request-fork-github/step2.png) ![Step 2](./step2.png)
## 3. Track the original repository as a remote of the fork ## 3. Track the original repository as a remote of the fork
@ -92,9 +89,9 @@ git push -u origin fix-readme-typo
You're now all ready to submit the improvement you've made to the project's maintainers for approval. Head over to the original repositories Pull Requests tab, and you should see an automatic suggestion from GitHub to create a pull request from your new branch. You're now all ready to submit the improvement you've made to the project's maintainers for approval. Head over to the original repositories Pull Requests tab, and you should see an automatic suggestion from GitHub to create a pull request from your new branch.
![Step 7.1](../public/static/images/notes/how-to-pull-request-fork-github/step7-1.png) ![Step 7.1](./step7-1.png)
![Step 7.2](../public/static/images/notes/how-to-pull-request-fork-github/step7-2.png) ![Step 7.2](./step7-2.png)
--- ---

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,17 +1,14 @@
--- import featuredImage from "./screen-shot-2018-12-07-at-2-04-04-pm.png";
title: "How To: Shrink a Linux Virtual Machine Disk with VMware"
date: 2018-12-04 19:10:04-0400
description: "VMware is bad at shrinking Linux VMs when space is freed up. How to optimize and shrink virtual disks."
tags:
- How To
- Linux
- Tutorial
- Virtual Machines
- VMware
image: "/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png"
---
![`df -dh` = WTF](../public/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-2-04-04-pm.png) export const frontmatter = {
title: "How To: Shrink a Linux Virtual Machine Disk with VMware",
date: "2018-12-04 19:10:04-0400",
description: "VMware is bad at shrinking Linux VMs when space is freed up. How to optimize and shrink virtual disks.",
tags: ["How To", "Linux", "Tutorial", "Virtual Machines", "VMware"],
image: featuredImage.src,
};
![`df -dh` = WTF](./screen-shot-2018-12-07-at-2-04-04-pm.png)
**[VMware Workstation](https://www.vmware.com/products/workstation-pro.html)** and **[Fusion](https://www.vmware.com/products/fusion.html)** normally work hard to minimize the size of virtual hard disks for optimizing the amount of storage needed on your host machine . On Windows virtual machines, [VMware has a "clean up" function](https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html), which detects newly unused space and makes the size of the virtual hard disk smaller accordingly. You'll notice that even if you create a virtual machine with a capacity of 60 GB, for example, the actual size of the VMDK file will dynamically resize to fit the usage of the guest operating system. 60 GB is simply the maximum amount of storage allowed; if your guest operating system and its files amount to 20 GB, the VMDK file will simply be 20 GB. **[VMware Workstation](https://www.vmware.com/products/workstation-pro.html)** and **[Fusion](https://www.vmware.com/products/fusion.html)** normally work hard to minimize the size of virtual hard disks for optimizing the amount of storage needed on your host machine . On Windows virtual machines, [VMware has a "clean up" function](https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html), which detects newly unused space and makes the size of the virtual hard disk smaller accordingly. You'll notice that even if you create a virtual machine with a capacity of 60 GB, for example, the actual size of the VMDK file will dynamically resize to fit the usage of the guest operating system. 60 GB is simply the maximum amount of storage allowed; if your guest operating system and its files amount to 20 GB, the VMDK file will simply be 20 GB.
@ -71,7 +68,7 @@ VMware on macOS makes this a little tricky, since it packages VMs in what looks
We need to right click on the .vmwarevm "file," and select **Show Package Contents** to see what's really in there. You should see the actual .VMDK file sitting there — normally we're looking for the plain VMDK file (named _Virtual Disk.vmdk_ by default) without a bunch of numbers after it, but if you have snapshots associated with your VM, this might not be the file we actually want. But run the command below with it anyways, and the output will tell you if you need to use a different file. We need to right click on the .vmwarevm "file," and select **Show Package Contents** to see what's really in there. You should see the actual .VMDK file sitting there — normally we're looking for the plain VMDK file (named _Virtual Disk.vmdk_ by default) without a bunch of numbers after it, but if you have snapshots associated with your VM, this might not be the file we actually want. But run the command below with it anyways, and the output will tell you if you need to use a different file.
![Finding .vmwarevm in Finder](../public/static/images/notes/how-to-shrink-linux-virtual-disk-vmware/screen-shot-2018-12-07-at-1-58-42-pm.png) ![Finding .vmwarevm in Finder](./screen-shot-2018-12-07-at-1-58-42-pm.png)
Now, we're going to run our final command in our **host** terminal, so open that up. Linux installations of VMware Workstation should have a simple map to the _vmware-vdiskmanager_ utility that you can run anywhere, but on macOS we need to tell it exactly where that's located: in the Applications folder, where Fusion is installed. Now, we're going to run our final command in our **host** terminal, so open that up. Linux installations of VMware Workstation should have a simple map to the _vmware-vdiskmanager_ utility that you can run anywhere, but on macOS we need to tell it exactly where that's located: in the Applications folder, where Fusion is installed.

View File

@ -1,17 +1,15 @@
--- import featuredImage from "./web-vitals.png";
title: "Revenge of the JavaScript: Moving from Hugo to Next.js"
date: 2022-04-07 10:53:33-0400
description: "The next chapter in this website's history of overengineering, from static HTML with Hugo to React everywhere with Next.js."
tags:
- React
- JavaScript
- Next.js
- Hugo
- Meta
image: "/static/images/notes/hugo-to-nextjs/web-vitals.png"
---
![Pull Request #711](../public/static/images/notes/hugo-to-nextjs/pr.png) export const frontmatter = {
title: "Revenge of the JavaScript: Moving from Hugo to Next.js",
date: "2022-04-07 10:53:33-0400",
description:
"The next chapter in this website's history of overengineering, from static HTML with Hugo to React everywhere with Next.js.",
tags: ["React", "JavaScript", "Next.js", "Hugo", "Meta"],
image: featuredImage.src,
};
![Pull Request #711](./pr.png)
I'll say right off the bat: this website has a _loooong_ history of going overboard with its tech stack. I use this domain as a vehicle to [learn new things](https://www.jvt.me/talks/overengineering-your-personal-website/), and given [how frequently](https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/) the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly. I'll say right off the bat: this website has a _loooong_ history of going overboard with its tech stack. I use this domain as a vehicle to [learn new things](https://www.jvt.me/talks/overengineering-your-personal-website/), and given [how frequently](https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/) the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly.
@ -30,7 +28,7 @@ Enter [**Next.js**](https://nextjs.org/), which caught my eye over other JS-cent
- **Fewer `devDependencies` and consolidated build tooling.** I don't want to look at another Gulp task for as long as possible. Next's [built-in](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config) Webpack and Babel support has come in clutch here. - **Fewer `devDependencies` and consolidated build tooling.** I don't want to look at another Gulp task for as long as possible. Next's [built-in](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config) Webpack and Babel support has come in clutch here.
- **Same (or better) [Lighthouse scores](https://web.dev/learn/#lighthouse).** The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I'll be using at this scale. And because of Next's static page generation (and [next-seo](https://github.com/garmeeh/next-seo)) nothing has changed in the realm of SEO. - **Same (or better) [Lighthouse scores](https://web.dev/learn/#lighthouse).** The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I'll be using at this scale. And because of Next's static page generation (and [next-seo](https://github.com/garmeeh/next-seo)) nothing has changed in the realm of SEO.
![Vercel web vitals analytics](../public/static/images/notes/hugo-to-nextjs/web-vitals.png) ![Vercel web vitals analytics](./web-vitals.png)
## Things I still miss from Hugo ## Things I still miss from Hugo

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Some files were not shown because too many files have changed in this diff Show More