feat: initial commit

This commit is contained in:
Cory Dransfeldt 2025-03-27 16:46:02 -07:00
commit e214116e40
No known key found for this signature in database
253 changed files with 17406 additions and 0 deletions

View file

@ -0,0 +1,41 @@
import ics from "ics";
export const albumReleasesCalendar = (collection) => {
const collectionData = collection.getAll()[0];
const { data } = collectionData;
const { albumReleases: { all }, globals: { url } } = data;
if (!all || all.length === 0) return "";
const events = all
.map((album) => {
const date = new Date(album.release_date);
if (isNaN(date.getTime())) return null;
const albumUrl = album.url?.includes("http") ? album.url : `${url}${album.url}`;
const artistUrl = album.artist.url?.includes("http") ? album.artust.url : `${url}${album.artist.url}`;
return {
start: [date.getFullYear(), date.getMonth() + 1, date.getDate()],
startInputType: "local",
startOutputType: "local",
title: `Release: ${album.artist.name} - ${album.title}`,
description: `Check out this new album release: ${albumUrl}. Read more about ${album.artist.name} at ${artistUrl}`,
url: albumUrl,
uid: `${album.release_timestamp}-${album.artist.name}-${album.title}`,
};
})
.filter((event) => event !== null);
const { error, value } = ics.createEvents(events, {
calName: "Album releases calendar • coryd.dev",
});
if (error) {
console.error("Error creating events: ", error);
return "";
}
return value;
};

View file

@ -0,0 +1,31 @@
import fs from "fs";
import path from "path";
import { minify } from "terser";
export const minifyJsComponents = async () => {
const scriptsDir = "dist/assets/scripts";
const minifyJsFilesInDir = async (dir) => {
const files = fs.readdirSync(dir);
for (const fileName of files) {
const filePath = path.join(dir, fileName);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
await minifyJsFilesInDir(filePath);
} else if (fileName.endsWith(".js")) {
const fileContent = fs.readFileSync(filePath, "utf8");
const minified = await minify(fileContent);
if (minified.error) {
console.error(`Error minifying ${filePath}:`, minified.error);
} else {
fs.writeFileSync(filePath, minified.code);
}
} else {
console.log(`No .js files to minify in ${filePath}`);
}
}
};
await minifyJsFilesInDir(scriptsDir);
};

21
config/filters/dates.js Normal file
View file

@ -0,0 +1,21 @@
export default {
stringToRFC822Date: (dateString) => {
const date = new Date(dateString);
if (isNaN(date.getTime())) return "";
const options = {
timeZone: "America/Los_Angeles",
weekday: "short",
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short",
};
return new Intl.DateTimeFormat("en-US", options).format(date);
},
};

28
config/filters/feeds.js Normal file
View file

@ -0,0 +1,28 @@
import { JSDOM } from "jsdom";
export default {
convertRelativeLinks: (htmlContent, domain) => {
if (!htmlContent || !domain) return htmlContent;
const dom = new JSDOM(htmlContent);
const document = dom.window.document;
document.querySelectorAll("a[href]").forEach(link => {
let href = link.getAttribute("href");
if (href.startsWith("#")) {
link.remove();
return;
}
if (!href.startsWith("http://") && !href.startsWith("https://"))
link.setAttribute("href", `${domain.replace(/\/$/, '')}/${href.replace(/^\/+/, '')}`);
});
return document.body.innerHTML;
},
generatePermalink: (url, baseUrl) => {
if (url?.includes("http") || !baseUrl) return url
return `${baseUrl}${url}`
}
}

24
config/filters/general.js Normal file
View file

@ -0,0 +1,24 @@
import truncateHtml from "truncate-html";
import { shuffleArray } from "../utilities/index.js";
export default {
encodeAmp: (string) => {
if (!string) return;
const pattern = /&(?!(?:[a-zA-Z]+|#[0-9]+|#x[0-9a-fA-F]+);)/g;
const replacement = "&";
return string.replace(pattern, replacement);
},
replaceQuotes: (string) => string.replace(/"/g, """),
htmlTruncate: (content, limit = 50) =>
truncateHtml(content, limit, {
byWords: true,
ellipsis: "...",
}),
shuffleArray,
pluralize: (count, string, trailing) => {
const countStr = String(count).replace(/,/g, "");
if (parseInt(countStr, 10) === 1) return string;
return `${string}s${trailing ? `${trailing}` : ''}`;
},
jsonEscape: (string) => JSON.stringify(string),
};

13
config/filters/index.js Normal file
View file

@ -0,0 +1,13 @@
import dates from "./dates.js";
import feeds from "./feeds.js"
import general from "./general.js";
import media from "./media.js";
import navigation from "./navigation.js";
export default {
...dates,
...feeds,
...general,
...media,
...navigation,
};

43
config/filters/media.js Normal file
View file

@ -0,0 +1,43 @@
export default {
filterBooksByStatus: (books, status) =>
books.filter((book) => book.status === status),
findFavoriteBooks: (books) =>
books.filter((book) => book.favorite === true),
bookYearLinks: (years) =>
years
.sort((a, b) => b.value - a.value)
.map(
(year, index) =>
`<a href="/books/years/${year.value}">${year.value}</a>${
index < years.length - 1 ? " • " : ""
}`
)
.join(""),
mediaLinks: (data, type, count = 10) => {
if (!data || !type) return "";
const dataSlice = data.slice(0, count);
if (dataSlice.length === 0) return null;
const buildLink = (item) => {
switch (type) {
case "genre":
return `<a href="${item.genre_url}">${item.genre_name}</a>`;
case "artist":
return `<a href="${item.url}">${item.name}</a>`;
case "book":
return `<a href="${item.url}">${item.title}</a>`;
default:
return "";
}
};
if (dataSlice.length === 1) return buildLink(dataSlice[0]);
const links = dataSlice.map(buildLink);
const allButLast = links.slice(0, -1).join(", ");
const last = links[links.length - 1];
return `${allButLast} and ${last}`;
},
};

View file

@ -0,0 +1,5 @@
export default {
isLinkActive: (category, page) =>
page.includes(category) &&
page.split("/").filter((a) => a !== "").length <= 1,
};

View file

@ -0,0 +1,33 @@
import fs from "node:fs/promises";
import path from "node:path";
import postcss from "postcss";
import postcssImport from "postcss-import";
import postcssImportExtGlob from "postcss-import-ext-glob";
import autoprefixer from "autoprefixer";
import cssnano from "cssnano";
export const cssConfig = (eleventyConfig) => {
eleventyConfig.addTemplateFormats("css");
eleventyConfig.addExtension("css", {
outputFileExtension: "css",
compile: async (inputContent, inputPath) => {
const outputPath = "dist/assets/css/index.css";
if (inputPath.endsWith("index.css")) {
return async () => {
let result = await postcss([
postcssImportExtGlob,
postcssImport,
autoprefixer,
cssnano,
]).process(inputContent, { from: inputPath });
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, result.css);
return result.css;
};
}
},
});
};

4
config/plugins/index.js Normal file
View file

@ -0,0 +1,4 @@
import { cssConfig } from "./css-config.js";
import { markdownLib } from "./markdown.js";
export default { cssConfig, markdownLib };

View file

@ -0,0 +1,25 @@
import markdownIt from "markdown-it";
import markdownItAnchor from "markdown-it-anchor";
import markdownItFootnote from "markdown-it-footnote";
import markdownItLinkAttributes from "markdown-it-link-attributes";
import markdownItPrism from "markdown-it-prism";
export const markdownLib = markdownIt({ html: true, linkify: true })
.use(markdownItAnchor, {
level: [1, 2],
permalink: markdownItAnchor.permalink.headerLink({
safariReaderFix: true,
}),
})
.use(markdownItLinkAttributes, [
{
matcher(href) {
return href.match(/^https?:\/\//);
},
attrs: {
rel: "noopener",
},
},
])
.use(markdownItFootnote)
.use(markdownItPrism);

10
config/utilities/index.js Normal file
View file

@ -0,0 +1,10 @@
export const shuffleArray = (array) => {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = temp;
}
return shuffled;
};