feat(*.liquid): apply prettier to liquid templates

- offer to create tag when none is found while adding a link from cli
- fix tag display in search
This commit is contained in:
Cory Dransfeldt 2025-06-16 14:40:54 -07:00
parent 49e21d574e
commit efe701f939
No known key found for this signature in database
112 changed files with 1319 additions and 1134 deletions

View file

@ -14,5 +14,7 @@ vendor/
# env # env
.env .env
# php # liquid with non-standard syntax
*.php **/*.php.liquid
**/feeds/*.liquid
**/meta/*.liquid

View file

@ -3,12 +3,13 @@
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,
"singleQuote": true,
"quoteProps": "as-needed", "quoteProps": "as-needed",
"trailingComma": "none", "trailingComma": "none",
"bracketSpacing": true, "bracketSpacing": true,
"arrowParens": "always", "arrowParens": "always",
"endOfLine": "lf", "endOfLine": "lf",
"proseWrap": "preserve", "proseWrap": "preserve",
"embeddedLanguageFormatting": "auto" "embeddedLanguageFormatting": "auto",
"singleQuote": false,
"plugins": ["@shopify/prettier-plugin-liquid"]
} }

View file

@ -1,42 +1,42 @@
#!/usr/bin/env node #!/usr/bin/env node
import { program } from 'commander'; import { program } from "commander";
import chalk from 'chalk'; import chalk from "chalk";
import figlet from 'figlet'; import figlet from "figlet";
import { loadConfig } from '../lib/config.js'; import { loadConfig } from "../lib/config.js";
import { handleExitError } from '../lib/handlers.js'; import { handleExitError } from "../lib/handlers.js";
import { runRootScript } from '../lib/runScript.js'; import { runRootScript } from "../lib/runScript.js";
import { runJobsMenu } from '../lib/jobs.js'; import { runJobsMenu } from "../lib/jobs.js";
import { runTasksMenu } from '../lib/tasks/index.js'; import { runTasksMenu } from "../lib/tasks/index.js";
process.on('unhandledRejection', (err) => handleExitError(err, 'Unhandled rejection')); process.on("unhandledRejection", (err) => handleExitError(err, "Unhandled rejection"));
process.on('uncaughtException', (err) => handleExitError(err, 'Uncaught exception')); process.on("uncaughtException", (err) => handleExitError(err, "Uncaught exception"));
program program
.name('coryd') .name("coryd")
.description('🪄 Handle tasks, run commands or jobs, download things and have fun.') .description("🪄 Handle tasks, run commands or jobs, download things and have fun.")
.version('3.2.5'); .version("3.2.5");
program program
.command('init') .command("init")
.description('Initialize CLI and populate required config.') .description("Initialize CLI and populate required config.")
.action(async () => { .action(async () => {
const { initConfig } = await import('../lib/config.js'); const { initConfig } = await import("../lib/config.js");
await initConfig(); await initConfig();
}); });
program.command('run [script]').description('Run site scripts and commands.').action(runRootScript); program.command("run [script]").description("Run site scripts and commands.").action(runRootScript);
program.command('tasks').description('Handle repeated tasks.').action(runTasksMenu); program.command("tasks").description("Handle repeated tasks.").action(runTasksMenu);
program.command('jobs').description('Trigger jobs and scripts.').action(runJobsMenu); program.command("jobs").description("Trigger jobs and scripts.").action(runJobsMenu);
program program
.command('download') .command("download")
.description('Download, name and store image assets.') .description("Download, name and store image assets.")
.action(async () => { .action(async () => {
const { downloadAsset } = await import('../lib/download.js'); const { downloadAsset } = await import("../lib/download.js");
await downloadAsset(); await downloadAsset();
}); });
if (process.argv.length <= 2) { if (process.argv.length <= 2) {
const ascii = figlet.textSync('coryd.dev', { horizontalLayout: 'default' }); const ascii = figlet.textSync("coryd.dev", { horizontalLayout: "default" });
console.log(chalk.hex('#3364ff')(ascii)); console.log(chalk.hex("#3364ff")(ascii));
console.log(); console.log();
program.outputHelp(); program.outputHelp();
process.exit(0); process.exit(0);

View file

@ -1,22 +1,22 @@
import fs from 'fs-extra'; import fs from "fs-extra";
import path from 'path'; import path from "path";
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import dotenv from 'dotenv'; import dotenv from "dotenv";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, '..'); const rootDir = path.resolve(__dirname, "..");
const CACHE_DIR = path.join(rootDir, '.cache'); const CACHE_DIR = path.join(rootDir, ".cache");
const CONFIG_PATH = path.join(CACHE_DIR, 'config.json'); const CONFIG_PATH = path.join(CACHE_DIR, "config.json");
export const MEDIA_TYPES = [ export const MEDIA_TYPES = [
{ key: 'movie', label: 'movie', folder: 'movies' }, { key: "movie", label: "movie", folder: "movies" },
{ key: 'show', label: 'tv show', folder: 'shows' } { key: "show", label: "tv show", folder: "shows" }
]; ];
const ASSET_TYPES = ['poster', 'backdrop']; const ASSET_TYPES = ["poster", "backdrop"];
dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env') }); dotenv.config({ path: path.resolve(__dirname, "..", "..", ".env") });
export const initConfig = async () => { export const initConfig = async () => {
const existingConfig = (await fs.pathExists(CONFIG_PATH)) ? await fs.readJson(CONFIG_PATH) : {}; const existingConfig = (await fs.pathExists(CONFIG_PATH)) ? await fs.readJson(CONFIG_PATH) : {};
@ -25,8 +25,8 @@ export const initConfig = async () => {
if (config.storageDir) { if (config.storageDir) {
const { updateStorage } = await inquirer.prompt([ const { updateStorage } = await inquirer.prompt([
{ {
type: 'confirm', type: "confirm",
name: 'updateStorage', name: "updateStorage",
message: `Storage directory is already set to "${config.storageDir}". Do you want to update it?`, message: `Storage directory is already set to "${config.storageDir}". Do you want to update it?`,
default: false default: false
} }
@ -35,8 +35,8 @@ export const initConfig = async () => {
if (updateStorage) { if (updateStorage) {
const { storageDir } = await inquirer.prompt([ const { storageDir } = await inquirer.prompt([
{ {
name: 'storageDir', name: "storageDir",
message: 'Where is your storage root directory?', message: "Where is your storage root directory?",
validate: fs.pathExists validate: fs.pathExists
} }
]); ]);
@ -46,8 +46,8 @@ export const initConfig = async () => {
} else { } else {
const { storageDir } = await inquirer.prompt([ const { storageDir } = await inquirer.prompt([
{ {
name: 'storageDir', name: "storageDir",
message: 'Where is your storage root directory?', message: "Where is your storage root directory?",
validate: fs.pathExists validate: fs.pathExists
} }
]); ]);
@ -57,9 +57,9 @@ export const initConfig = async () => {
const { customize } = await inquirer.prompt([ const { customize } = await inquirer.prompt([
{ {
type: 'confirm', type: "confirm",
name: 'customize', name: "customize",
message: 'Do you want to customize default media paths?', message: "Do you want to customize default media paths?",
default: false default: false
} }
]); ]);
@ -70,14 +70,14 @@ export const initConfig = async () => {
config.mediaPaths[key] = {}; config.mediaPaths[key] = {};
for (const assetType of ASSET_TYPES) { for (const assetType of ASSET_TYPES) {
const assetFolder = assetType === 'poster' ? '' : `/${assetType}s`; const assetFolder = assetType === "poster" ? "" : `/${assetType}s`;
const defaultPath = `Media assets/${folder}${assetFolder}`.replace(/\/$/, ''); const defaultPath = `Media assets/${folder}${assetFolder}`.replace(/\/$/, "");
let subpath = defaultPath; let subpath = defaultPath;
if (customize) { if (customize) {
const response = await inquirer.prompt([ const response = await inquirer.prompt([
{ {
name: 'subpath', name: "subpath",
message: `Subpath for ${label}/${assetType} (relative to storage root):`, message: `Subpath for ${label}/${assetType} (relative to storage root):`,
default: defaultPath default: defaultPath
} }
@ -94,43 +94,43 @@ export const initConfig = async () => {
? ( ? (
await inquirer.prompt([ await inquirer.prompt([
{ {
name: 'artistPath', name: "artistPath",
message: 'Subpath for artist images (relative to storage root):', message: "Subpath for artist images (relative to storage root):",
default: 'Media assets/artists' default: "Media assets/artists"
} }
]) ])
).artistPath ).artistPath
: 'Media assets/artists'; : "Media assets/artists";
config.albumPath = customize config.albumPath = customize
? ( ? (
await inquirer.prompt([ await inquirer.prompt([
{ {
name: 'albumPath', name: "albumPath",
message: 'Subpath for album images (relative to storage root):', message: "Subpath for album images (relative to storage root):",
default: 'Media assets/albums' default: "Media assets/albums"
} }
]) ])
).albumPath ).albumPath
: 'Media assets/albums'; : "Media assets/albums";
config.bookPath = customize config.bookPath = customize
? ( ? (
await inquirer.prompt([ await inquirer.prompt([
{ {
name: 'bookPath', name: "bookPath",
message: 'Subpath for book images (relative to storage root):', message: "Subpath for book images (relative to storage root):",
default: 'Media assets/books' default: "Media assets/books"
} }
]) ])
).bookPath ).bookPath
: 'Media assets/books'; : "Media assets/books";
if (config.directus?.apiUrl) { if (config.directus?.apiUrl) {
const { updateApiUrl } = await inquirer.prompt([ const { updateApiUrl } = await inquirer.prompt([
{ {
type: 'confirm', type: "confirm",
name: 'updateApiUrl', name: "updateApiUrl",
message: `Directus API URL is already set to "${config.directus.apiUrl}". Do you want to update it?`, message: `Directus API URL is already set to "${config.directus.apiUrl}". Do you want to update it?`,
default: false default: false
} }
@ -139,9 +139,9 @@ export const initConfig = async () => {
if (updateApiUrl) { if (updateApiUrl) {
const { apiUrl } = await inquirer.prompt([ const { apiUrl } = await inquirer.prompt([
{ {
name: 'apiUrl', name: "apiUrl",
message: 'Enter your Directus instance URL:', message: "Enter your Directus instance URL:",
validate: (input) => input.startsWith('http') || 'Must be a valid URL' validate: (input) => input.startsWith("http") || "Must be a valid URL"
} }
]); ]);
@ -150,9 +150,9 @@ export const initConfig = async () => {
} else { } else {
const { apiUrl } = await inquirer.prompt([ const { apiUrl } = await inquirer.prompt([
{ {
name: 'apiUrl', name: "apiUrl",
message: 'Enter your Directus URL:', message: "Enter your Directus URL:",
validate: (input) => input.startsWith('http') || 'Must be a valid URL' validate: (input) => input.startsWith("http") || "Must be a valid URL"
} }
]); ]);
@ -171,7 +171,7 @@ export const initConfig = async () => {
export const loadConfig = async () => { export const loadConfig = async () => {
if (!(await fs.pathExists(CONFIG_PATH))) { if (!(await fs.pathExists(CONFIG_PATH))) {
console.error('❌ Config not found. Run \`coryd init\` first.'); console.error("❌ Config not found. Run \`coryd init\` first.");
process.exit(1); process.exit(1);
} }
@ -183,15 +183,15 @@ const fetchGlobals = async () => {
const POSTGREST_API_KEY = process.env.POSTGREST_API_KEY; const POSTGREST_API_KEY = process.env.POSTGREST_API_KEY;
if (!POSTGREST_URL || !POSTGREST_API_KEY) { if (!POSTGREST_URL || !POSTGREST_API_KEY) {
console.warn('⚠️ Missing POSTGREST_URL or POSTGREST_API_KEY in env, skipping globals fetch.'); console.warn("⚠️ Missing POSTGREST_URL or POSTGREST_API_KEY in env, skipping globals fetch.");
return {}; return {};
} }
try { try {
const res = await fetch(`${POSTGREST_URL}/optimized_globals?select=*`, { const res = await fetch(`${POSTGREST_URL}/optimized_globals?select=*`, {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
}); });
@ -201,7 +201,7 @@ const fetchGlobals = async () => {
const data = await res.json(); const data = await res.json();
return data[0] || {}; return data[0] || {};
} catch (err) { } catch (err) {
console.error('❌ Error fetching globals:', err.message); console.error("❌ Error fetching globals:", err.message);
return {}; return {};
} }
}; };

View file

@ -1,11 +1,11 @@
import path from 'path'; import path from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import dotenv from 'dotenv'; import dotenv from "dotenv";
import { createDirectus, staticToken, rest } from '@directus/sdk'; import { createDirectus, staticToken, rest } from "@directus/sdk";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.resolve(__dirname, '..', '..', '..', '.env') }); dotenv.config({ path: path.resolve(__dirname, "..", "..", "..", ".env") });
let directus; let directus;
let API_URL; let API_URL;
@ -15,13 +15,13 @@ export const initDirectusClient = (config) => {
const token = process.env.DIRECTUS_API_TOKEN; const token = process.env.DIRECTUS_API_TOKEN;
if (!API_URL || !token) throw new Error('Missing Directus API URL or token.'); if (!API_URL || !token) throw new Error("Missing Directus API URL or token.");
directus = createDirectus(API_URL).with(staticToken(process.env.DIRECTUS_API_TOKEN)).with(rest()); directus = createDirectus(API_URL).with(staticToken(process.env.DIRECTUS_API_TOKEN)).with(rest());
}; };
export const getDirectusClient = () => { export const getDirectusClient = () => {
if (!directus) throw new Error('Directus client not initialized.'); if (!directus) throw new Error("Directus client not initialized.");
return directus; return directus;
}; };
@ -31,7 +31,7 @@ const request = async (method, endpoint, body = null) => {
const res = await fetch(`${API_URL}/items/${endpoint}`, { const res = await fetch(`${API_URL}/items/${endpoint}`, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}` Authorization: `Bearer ${API_TOKEN}`
}, },
body: body ? JSON.stringify(body) : null body: body ? JSON.stringify(body) : null
@ -46,13 +46,13 @@ const request = async (method, endpoint, body = null) => {
return await res.json(); return await res.json();
}; };
export const searchItems = async (collection, query = '', filters = {}) => { export const searchItems = async (collection, query = "", filters = {}) => {
const API_TOKEN = process.env.DIRECTUS_API_TOKEN; const API_TOKEN = process.env.DIRECTUS_API_TOKEN;
const params = new URLSearchParams(); const params = new URLSearchParams();
if (query) params.append('search', query); if (query) params.append("search", query);
params.append('limit', '50'); params.append("limit", "50");
for (const [field, value] of Object.entries(filters)) { for (const [field, value] of Object.entries(filters)) {
params.append(`filter[${field}][_eq]`, value); params.append(`filter[${field}][_eq]`, value);
@ -60,9 +60,9 @@ export const searchItems = async (collection, query = '', filters = {}) => {
try { try {
const res = await fetch(`${API_URL}/items/${collection}?${params.toString()}`, { const res = await fetch(`${API_URL}/items/${collection}?${params.toString()}`, {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}` Authorization: `Bearer ${API_TOKEN}`
} }
}); });
@ -77,6 +77,6 @@ export const searchItems = async (collection, query = '', filters = {}) => {
}; };
export const updateItem = async (collection, id, values) => export const updateItem = async (collection, id, values) =>
await request('PATCH', `${collection}/${id}`, values); await request("PATCH", `${collection}/${id}`, values);
export const createItem = async (collection, values) => await request('POST', collection, values); export const createItem = async (collection, values) => await request("POST", collection, values);

View file

@ -1,12 +1,12 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { searchItems } from '../directus/client.js'; import { searchItems } from "../directus/client.js";
export const promptForMultipleRelations = async (collection, label = collection) => { export const promptForMultipleRelations = async (collection, label = collection) => {
const selectedIds = new Set(); const selectedIds = new Set();
while (true) { while (true) {
const { query } = await inquirer.prompt({ const { query } = await inquirer.prompt({
name: 'query', name: "query",
message: `🔍 Search ${label} (or leave blank to finish):` message: `🔍 Search ${label} (or leave blank to finish):`
}); });
const trimmed = query.trim(); const trimmed = query.trim();
@ -22,8 +22,8 @@ export const promptForMultipleRelations = async (collection, label = collection)
} }
const { selected } = await inquirer.prompt({ const { selected } = await inquirer.prompt({
type: 'checkbox', type: "checkbox",
name: 'selected', name: "selected",
message: `✔ Select ${label} to add:`, message: `✔ Select ${label} to add:`,
choices: results.map((item) => ({ choices: results.map((item) => ({
name: item.name || item.title || item.id, name: item.name || item.title || item.id,
@ -34,8 +34,8 @@ export const promptForMultipleRelations = async (collection, label = collection)
selected.forEach((id) => selectedIds.add(id)); selected.forEach((id) => selectedIds.add(id));
const { again } = await inquirer.prompt({ const { again } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'again', name: "again",
message: `Search and add more ${label}?`, message: `Search and add more ${label}?`,
default: false default: false
}); });

View file

@ -1,10 +1,10 @@
import fs from 'fs-extra'; import fs from "fs-extra";
import path from 'path'; import path from "path";
import https from 'https'; import https from "https";
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import { loadConfig, MEDIA_TYPES } from './config.js'; import { loadConfig, MEDIA_TYPES } from "./config.js";
import { sanitizeMediaString } from './sanitize.js'; import { sanitizeMediaString } from "./sanitize.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const downloadImage = (url, dest) => const downloadImage = (url, dest) =>
@ -17,19 +17,19 @@ const downloadImage = (url, dest) =>
return reject(new Error(`Failed to download. Status: ${response.statusCode}`)); return reject(new Error(`Failed to download. Status: ${response.statusCode}`));
response.pipe(file); response.pipe(file);
file.on('finish', () => file.close(resolve)); file.on("finish", () => file.close(resolve));
}) })
.on('error', reject); .on("error", reject);
}); });
const isValidTMDBUrl = (val) => const isValidTMDBUrl = (val) =>
/^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || '❌ Must be a valid TMDB image url'; /^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || "❌ Must be a valid TMDB image url";
const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => { const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => {
await fs.ensureDir(path.dirname(finalPath)); await fs.ensureDir(path.dirname(finalPath));
if (await fs.pathExists(finalPath)) { if (await fs.pathExists(finalPath)) {
const { overwrite } = await inquirer.prompt({ const { overwrite } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'overwrite', name: "overwrite",
message: `${fileName} already exists. Overwrite?`, message: `${fileName} already exists. Overwrite?`,
default: false default: false
}); });
@ -49,29 +49,29 @@ const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => {
export const downloadWatchingImages = async () => { export const downloadWatchingImages = async () => {
const config = await loadConfig(); const config = await loadConfig();
const { mediaType } = await inquirer.prompt({ const { mediaType } = await inquirer.prompt({
type: 'list', type: "list",
name: 'mediaType', name: "mediaType",
message: 'Movie or a tv show?', message: "Movie or a tv show?",
choices: MEDIA_TYPES.map(({ key, label }) => ({ choices: MEDIA_TYPES.map(({ key, label }) => ({
name: label, name: label,
value: key value: key
})) }))
}); });
const { tmdbId } = await inquirer.prompt({ const { tmdbId } = await inquirer.prompt({
name: 'tmdbId', name: "tmdbId",
message: 'Enter the TMDB ID:' message: "Enter the TMDB ID:"
}); });
if (!tmdbId) { if (!tmdbId) {
console.warn('⚠️ TMDB ID is required.'); console.warn("⚠️ TMDB ID is required.");
return; return;
} }
const { posterUrl, backdropUrl } = await inquirer.prompt([ const { posterUrl, backdropUrl } = await inquirer.prompt([
{ {
name: 'posterUrl', name: "posterUrl",
message: 'Enter the poster url:', message: "Enter the poster url:",
validate: (val) => { validate: (val) => {
if (!val) return true; if (!val) return true;
@ -79,8 +79,8 @@ export const downloadWatchingImages = async () => {
} }
}, },
{ {
name: 'backdropUrl', name: "backdropUrl",
message: 'Enter the backdrop url:', message: "Enter the backdrop url:",
validate: (val) => { validate: (val) => {
if (!val) return true; if (!val) return true;
@ -89,14 +89,14 @@ export const downloadWatchingImages = async () => {
} }
]); ]);
const types = [ const types = [
{ type: 'poster', url: posterUrl }, { type: "poster", url: posterUrl },
{ type: 'backdrop', url: backdropUrl } { type: "backdrop", url: backdropUrl }
]; ];
for (const { type, url } of types) { for (const { type, url } of types) {
if (!url) continue; if (!url) continue;
const ext = path.extname(new URL(url).pathname) || '.jpg'; const ext = path.extname(new URL(url).pathname) || ".jpg";
const fileName = `${type}-${tmdbId}${ext}`; const fileName = `${type}-${tmdbId}${ext}`;
const targetSubPath = config.mediaPaths[mediaType][type]; const targetSubPath = config.mediaPaths[mediaType][type];
@ -116,34 +116,34 @@ export const downloadWatchingImages = async () => {
export const downloadArtistImage = async () => { export const downloadArtistImage = async () => {
const config = await loadConfig(); const config = await loadConfig();
const { artistName } = await inquirer.prompt({ const { artistName } = await inquirer.prompt({
name: 'artistName', name: "artistName",
message: 'Enter the artist name:' message: "Enter the artist name:"
}); });
if (!artistName) { if (!artistName) {
console.warn('⚠️ Artist name is required.'); console.warn("⚠️ Artist name is required.");
return; return;
} }
const { imageUrl } = await inquirer.prompt({ const { imageUrl } = await inquirer.prompt({
name: 'imageUrl', name: "imageUrl",
message: 'Enter the artist image url:', message: "Enter the artist image url:",
validate: (val) => { validate: (val) => {
try { try {
new URL(val); new URL(val);
return true; return true;
} catch { } catch {
return '❌ Must be a valid url.'; return "❌ Must be a valid url.";
} }
} }
}); });
const sanitizedName = sanitizeMediaString(artistName); const sanitizedName = sanitizeMediaString(artistName);
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
const fileName = `${sanitizedName}${ext}`; const fileName = `${sanitizedName}${ext}`;
const targetDir = path.join(config.storageDir, 'artists'); const targetDir = path.join(config.storageDir, "artists");
const finalPath = path.join(targetDir, fileName); const finalPath = path.join(targetDir, fileName);
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName); await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
@ -153,44 +153,44 @@ export const downloadAlbumImage = async () => {
const config = await loadConfig(); const config = await loadConfig();
const { artistName } = await inquirer.prompt({ const { artistName } = await inquirer.prompt({
name: 'artistName', name: "artistName",
message: 'Enter the artist name:' message: "Enter the artist name:"
}); });
if (!artistName) { if (!artistName) {
console.warn('⚠️ Artist name is required.'); console.warn("⚠️ Artist name is required.");
return; return;
} }
const { albumName } = await inquirer.prompt({ const { albumName } = await inquirer.prompt({
name: 'albumName', name: "albumName",
message: 'Enter the album name:' message: "Enter the album name:"
}); });
if (!albumName) { if (!albumName) {
console.warn('⚠️ Album name is required.'); console.warn("⚠️ Album name is required.");
return; return;
} }
const { imageUrl } = await inquirer.prompt({ const { imageUrl } = await inquirer.prompt({
name: 'imageUrl', name: "imageUrl",
message: 'Enter the album image url:', message: "Enter the album image url:",
validate: (val) => { validate: (val) => {
try { try {
new URL(val); new URL(val);
return true; return true;
} catch { } catch {
return '❌ Must be a valid url.'; return "❌ Must be a valid url.";
} }
} }
}); });
const artistSlug = sanitizeMediaString(artistName); const artistSlug = sanitizeMediaString(artistName);
const albumSlug = sanitizeMediaString(albumName); const albumSlug = sanitizeMediaString(albumName);
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
const fileName = `${artistSlug}-${albumSlug}${ext}`; const fileName = `${artistSlug}-${albumSlug}${ext}`;
const targetDir = path.join(config.storageDir, config.albumPath); const targetDir = path.join(config.storageDir, config.albumPath);
const finalPath = path.join(targetDir, fileName); const finalPath = path.join(targetDir, fileName);
@ -201,37 +201,37 @@ export const downloadAlbumImage = async () => {
export const downloadBookImage = async () => { export const downloadBookImage = async () => {
const config = await loadConfig(); const config = await loadConfig();
const { isbn } = await inquirer.prompt({ const { isbn } = await inquirer.prompt({
name: 'isbn', name: "isbn",
message: 'Enter the ISBN (no spaces):', message: "Enter the ISBN (no spaces):",
validate: (val) => validate: (val) =>
/^[a-zA-Z0-9-]+$/.test(val) || 'ISBN must contain only letters, numbers, or hyphens' /^[a-zA-Z0-9-]+$/.test(val) || "ISBN must contain only letters, numbers, or hyphens"
}); });
const { bookTitle } = await inquirer.prompt({ const { bookTitle } = await inquirer.prompt({
name: 'bookTitle', name: "bookTitle",
message: 'Enter the book title:' message: "Enter the book title:"
}); });
if (!bookTitle) { if (!bookTitle) {
console.warn('⚠️ Book title is required.'); console.warn("⚠️ Book title is required.");
return; return;
} }
const { imageUrl } = await inquirer.prompt({ const { imageUrl } = await inquirer.prompt({
name: 'imageUrl', name: "imageUrl",
message: 'Enter the book cover image URL:', message: "Enter the book cover image URL:",
validate: (val) => { validate: (val) => {
try { try {
new URL(val); new URL(val);
return true; return true;
} catch { } catch {
return 'Must be a valid URL'; return "Must be a valid URL";
} }
} }
}); });
const titleSlug = sanitizeMediaString(bookTitle); const titleSlug = sanitizeMediaString(bookTitle);
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
const fileName = `${isbn}-${titleSlug}${ext}`; const fileName = `${isbn}-${titleSlug}${ext}`;
const targetDir = path.join(config.storageDir, config.bookPath); const targetDir = path.join(config.storageDir, config.bookPath);
const finalPath = path.join(targetDir, fileName); const finalPath = path.join(targetDir, fileName);
@ -241,17 +241,17 @@ export const downloadBookImage = async () => {
export const downloadAsset = async () => { export const downloadAsset = async () => {
const { type } = await inquirer.prompt({ const { type } = await inquirer.prompt({
type: 'list', type: "list",
name: 'type', name: "type",
message: 'What type of asset are you downloading?', message: "What type of asset are you downloading?",
choices: ['movie/tv show', 'artist', 'album', 'book'] choices: ["movie/tv show", "artist", "album", "book"]
}); });
if (type === 'artist') { if (type === "artist") {
await downloadArtistImage(); await downloadArtistImage();
} else if (type === 'album') { } else if (type === "album") {
await downloadAlbumImage(); await downloadAlbumImage();
} else if (type === 'book') { } else if (type === "book") {
await downloadBookImage(); await downloadBookImage();
} else { } else {
await downloadWatchingImages(); await downloadWatchingImages();

View file

@ -1,11 +1,11 @@
export const handleExitError = (err, type = 'Unhandled error') => { export const handleExitError = (err, type = "Unhandled error") => {
const isExit = const isExit =
err?.name === 'ExitPromptError' || err?.name === "ExitPromptError" ||
err?.code === 'ERR_CANCELED' || err?.code === "ERR_CANCELED" ||
err?.message?.includes('SIGINT'); err?.message?.includes("SIGINT");
if (isExit) { if (isExit) {
console.log('\n👋 Exiting. Cya!\n'); console.log("\n👋 Exiting. Cya!\n");
process.exit(0); process.exit(0);
} }

View file

@ -1,110 +1,110 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import path from 'path'; import path from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import dotenv from 'dotenv'; import dotenv from "dotenv";
import { loadConfig } from './config.js'; import { loadConfig } from "./config.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env') }); dotenv.config({ path: path.resolve(__dirname, "..", "..", ".env") });
export const runJobsMenu = async () => { export const runJobsMenu = async () => {
const config = await loadConfig(); const config = await loadConfig();
const JOBS = [ const JOBS = [
{ {
name: '🛠 Rebuild site', name: "🛠 Rebuild site",
type: 'curl', type: "curl",
urlEnvVar: 'SITE_REBUILD_WEBHOOK', urlEnvVar: "SITE_REBUILD_WEBHOOK",
tokenEnvVar: 'DIRECTUS_API_TOKEN', tokenEnvVar: "DIRECTUS_API_TOKEN",
method: 'GET' method: "GET"
}, },
{ {
name: '💿 Scrobble listens from Navidrome', name: "💿 Scrobble listens from Navidrome",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/scrobble.php`, apiUrl: `${config.url}/api/scrobble.php`,
tokenEnvVar: 'NAVIDROME_SCROBBLE_TOKEN', tokenEnvVar: "NAVIDROME_SCROBBLE_TOKEN",
method: 'POST' method: "POST"
}, },
{ {
name: '🎧 Update total plays', name: "🎧 Update total plays",
type: 'curl', type: "curl",
urlEnvVar: 'TOTAL_PLAYS_WEBHOOK', urlEnvVar: "TOTAL_PLAYS_WEBHOOK",
tokenEnvVar: 'DIRECTUS_API_TOKEN', tokenEnvVar: "DIRECTUS_API_TOKEN",
method: 'GET' method: "GET"
}, },
{ {
name: '🐘 Send posts to Mastodon', name: "🐘 Send posts to Mastodon",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/mastodon.php`, apiUrl: `${config.url}/api/mastodon.php`,
tokenEnvVar: 'MASTODON_SYNDICATION_TOKEN', tokenEnvVar: "MASTODON_SYNDICATION_TOKEN",
method: 'POST' method: "POST"
}, },
{ {
name: '🎤 Import artist from Navidrome', name: "🎤 Import artist from Navidrome",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/artist-import.php`, apiUrl: `${config.url}/api/artist-import.php`,
tokenEnvVar: 'ARTIST_IMPORT_TOKEN', tokenEnvVar: "ARTIST_IMPORT_TOKEN",
method: 'POST', method: "POST",
paramsPrompt: [ paramsPrompt: [
{ {
type: 'input', type: "input",
name: 'artistId', name: "artistId",
message: 'Enter the Navidrome artist ID:', message: "Enter the Navidrome artist ID:",
validate: (input) => (input ? true : 'Artist ID is required') validate: (input) => (input ? true : "Artist ID is required")
} }
] ]
}, },
{ {
name: '📖 Import book', name: "📖 Import book",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/book-import.php`, apiUrl: `${config.url}/api/book-import.php`,
tokenEnvVar: 'BOOK_IMPORT_TOKEN', tokenEnvVar: "BOOK_IMPORT_TOKEN",
method: 'POST', method: "POST",
paramsPrompt: [ paramsPrompt: [
{ {
type: 'input', type: "input",
name: 'isbn', name: "isbn",
message: "Enter the book's ISBN:", message: "Enter the book's ISBN:",
validate: (input) => (input ? true : 'ISBN is required') validate: (input) => (input ? true : "ISBN is required")
} }
] ]
}, },
{ {
name: '📽 Import a movie or tv show', name: "📽 Import a movie or tv show",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/watching-import.php`, apiUrl: `${config.url}/api/watching-import.php`,
tokenEnvVar: 'WATCHING_IMPORT_TOKEN', tokenEnvVar: "WATCHING_IMPORT_TOKEN",
method: 'POST', method: "POST",
tokenIncludeInParams: true, tokenIncludeInParams: true,
paramsPrompt: [ paramsPrompt: [
{ {
type: 'input', type: "input",
name: 'tmdb_id', name: "tmdb_id",
message: 'Enter the TMDB ID:', message: "Enter the TMDB ID:",
validate: (input) => (/^\d+$/.test(input) ? true : 'Please enter a valid TMDB ID') validate: (input) => (/^\d+$/.test(input) ? true : "Please enter a valid TMDB ID")
}, },
{ {
type: 'list', type: "list",
name: 'media_type', name: "media_type",
message: 'Is this a movie or tv show?', message: "Is this a movie or tv show?",
choices: ['movie', 'tv'] choices: ["movie", "tv"]
} }
] ]
}, },
{ {
name: '📺 Import upcoming TV seasons', name: "📺 Import upcoming TV seasons",
type: 'curl', type: "curl",
apiUrl: `${config.url}/api/seasons-import.php`, apiUrl: `${config.url}/api/seasons-import.php`,
tokenEnvVar: 'SEASONS_IMPORT_TOKEN', tokenEnvVar: "SEASONS_IMPORT_TOKEN",
method: 'POST' method: "POST"
} }
]; ];
const { selectedJob } = await inquirer.prompt([ const { selectedJob } = await inquirer.prompt([
{ {
type: 'list', type: "list",
name: 'selectedJob', name: "selectedJob",
message: 'Select a job to run:', message: "Select a job to run:",
choices: JOBS.map((job, index) => ({ choices: JOBS.map((job, index) => ({
name: job.name, name: job.name,
value: index value: index
@ -114,7 +114,7 @@ export const runJobsMenu = async () => {
const job = JOBS[selectedJob]; const job = JOBS[selectedJob];
if (job.type === 'curl') { if (job.type === "curl") {
let params = null; let params = null;
if (job.paramsPrompt) { if (job.paramsPrompt) {
@ -132,9 +132,9 @@ export const runJobsMenu = async () => {
const runCurl = async ({ const runCurl = async ({
urlEnvVar, urlEnvVar,
apiUrl = '', apiUrl = "",
tokenEnvVar, tokenEnvVar,
method = 'POST', method = "POST",
name, name,
params = null params = null
}) => { }) => {
@ -151,7 +151,7 @@ const runCurl = async ({
const res = await fetch(url, { const res = await fetch(url, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
...(token && { Authorization: `Bearer ${token}` }) ...(token && { Authorization: `Bearer ${token}` })
}, },
...(params && { body: JSON.stringify(params) }) ...(params && { body: JSON.stringify(params) })

View file

@ -1,13 +1,13 @@
import fs from 'fs-extra'; import fs from "fs-extra";
import path from 'path'; import path from "path";
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { execSync } from 'child_process'; import { execSync } from "child_process";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const rootDir = path.resolve(__dirname, '..', '..'); const rootDir = path.resolve(__dirname, "..", "..");
const packageJsonPath = path.join(rootDir, 'package.json'); const packageJsonPath = path.join(rootDir, "package.json");
export const runRootScript = async (scriptArg) => { export const runRootScript = async (scriptArg) => {
const pkg = await fs.readJson(packageJsonPath); const pkg = await fs.readJson(packageJsonPath);
@ -17,9 +17,9 @@ export const runRootScript = async (scriptArg) => {
if (!script) { if (!script) {
const { selected } = await inquirer.prompt([ const { selected } = await inquirer.prompt([
{ {
type: 'list', type: "list",
name: 'selected', name: "selected",
message: 'Select a script to run:', message: "Select a script to run:",
choices: Object.keys(scripts) choices: Object.keys(scripts)
} }
]); ]);
@ -34,7 +34,7 @@ export const runRootScript = async (scriptArg) => {
try { try {
execSync(`npm run ${script}`, { execSync(`npm run ${script}`, {
stdio: 'inherit', stdio: "inherit",
cwd: rootDir cwd: rootDir
}); });
} catch (err) { } catch (err) {

View file

@ -1,11 +1,11 @@
import { transliterate } from 'transliteration'; import { transliterate } from "transliteration";
export const sanitizeMediaString = (input) => { export const sanitizeMediaString = (input) => {
const ascii = transliterate(input); const ascii = transliterate(input);
const cleaned = ascii.replace(/[^a-zA-Z0-9\s-]/g, ''); const cleaned = ascii.replace(/[^a-zA-Z0-9\s-]/g, "");
const slugified = cleaned.replace(/[\s-]+/g, '-').toLowerCase(); const slugified = cleaned.replace(/[\s-]+/g, "-").toLowerCase();
return slugified.replace(/^-+|-+$/g, ''); return slugified.replace(/^-+|-+$/g, "");
}; };
export const removeUrlProtocol = (url) => (url ? url.replace(/^https?:\/\//, '') : ''); export const removeUrlProtocol = (url) => (url ? url.replace(/^https?:\/\//, "") : "");

View file

@ -1,38 +1,38 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { loadConfig } from '../config.js'; import { loadConfig } from "../config.js";
import { initDirectusClient, searchItems, createItem } from '../directus/client.js'; import { initDirectusClient, searchItems, createItem } from "../directus/client.js";
export const addBlockedRobot = async () => { export const addBlockedRobot = async () => {
const config = await loadConfig(); const config = await loadConfig();
initDirectusClient(config); initDirectusClient(config);
const robots = await searchItems('robots', '/'); const robots = await searchItems("robots", "/");
let rootRobot = robots.find((r) => r.path === '/'); let rootRobot = robots.find((r) => r.path === "/");
if (!rootRobot) { if (!rootRobot) {
console.log(' No robots entry for `/` found. Creating one...'); console.log(" No robots entry for `/` found. Creating one...");
const newRobot = await createItem('robots', { path: '/' }); const newRobot = await createItem("robots", { path: "/" });
rootRobot = newRobot.data || newRobot; rootRobot = newRobot.data || newRobot;
console.log('✅ Created robots rule for `/`'); console.log("✅ Created robots rule for `/`");
} }
const { userAgent } = await inquirer.prompt({ const { userAgent } = await inquirer.prompt({
name: 'userAgent', name: "userAgent",
message: '🤖 Enter the user-agent string to block:', message: "🤖 Enter the user-agent string to block:",
validate: (input) => !!input || 'User-agent cannot be empty' validate: (input) => !!input || "User-agent cannot be empty"
}); });
const createdAgent = await createItem('user_agents', { const createdAgent = await createItem("user_agents", {
user_agent: userAgent user_agent: userAgent
}); });
const agentId = createdAgent.data?.id || createdAgent.id; const agentId = createdAgent.data?.id || createdAgent.id;
await createItem('robots_user_agents', { await createItem("robots_user_agents", {
robots_id: rootRobot.id, robots_id: rootRobot.id,
user_agents_id: agentId user_agents_id: agentId
}); });

View file

@ -1,12 +1,12 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { loadConfig } from '../config.js'; import { loadConfig } from "../config.js";
import { import {
initDirectusClient, initDirectusClient,
getDirectusClient, getDirectusClient,
searchItems, searchItems,
createItem, createItem,
updateItem updateItem
} from '../directus/client.js'; } from "../directus/client.js";
export const addEpisodeToShow = async () => { export const addEpisodeToShow = async () => {
const config = await loadConfig(); const config = await loadConfig();
@ -15,21 +15,21 @@ export const addEpisodeToShow = async () => {
const directus = getDirectusClient(); const directus = getDirectusClient();
const showResults = await inquirer.prompt({ const showResults = await inquirer.prompt({
name: 'query', name: "query",
message: 'Search for a show:' message: "Search for a show:"
}); });
const matches = await searchItems('shows', showResults.query); const matches = await searchItems("shows", showResults.query);
if (!matches.length) { if (!matches.length) {
console.warn('⚠️ No matching shows found.'); console.warn("⚠️ No matching shows found.");
return; return;
} }
const { showId } = await inquirer.prompt({ const { showId } = await inquirer.prompt({
type: 'list', type: "list",
name: 'showId', name: "showId",
message: 'Select a show:', message: "Select a show:",
choices: matches.map((s) => ({ choices: matches.map((s) => ({
name: s.title || s.name || s.id, name: s.title || s.name || s.id,
value: s.id value: s.id
@ -37,23 +37,23 @@ export const addEpisodeToShow = async () => {
}); });
const { season_number, episode_number, plays } = await inquirer.prompt([ const { season_number, episode_number, plays } = await inquirer.prompt([
{ {
name: 'season_number', name: "season_number",
message: 'Season number:', message: "Season number:",
validate: (val) => !isNaN(val) validate: (val) => !isNaN(val)
}, },
{ {
name: 'episode_number', name: "episode_number",
message: 'Episode number:', message: "Episode number:",
validate: (val) => !isNaN(val) validate: (val) => !isNaN(val)
}, },
{ {
name: 'plays', name: "plays",
message: 'Play count:', message: "Play count:",
default: 0, default: 0,
validate: (val) => !isNaN(val) validate: (val) => !isNaN(val)
} }
]); ]);
const existing = await searchItems('episodes', `${season_number} ${episode_number}`); const existing = await searchItems("episodes", `${season_number} ${episode_number}`);
const match = existing.find( const match = existing.find(
(e) => (e) =>
Number(e.season_number) === Number(season_number) && Number(e.season_number) === Number(season_number) &&
@ -63,21 +63,21 @@ export const addEpisodeToShow = async () => {
if (match) { if (match) {
const { update } = await inquirer.prompt({ const { update } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'update', name: "update",
message: `Episode exists. Update play count from ${match.plays ?? 0} to ${plays}?`, message: `Episode exists. Update play count from ${match.plays ?? 0} to ${plays}?`,
default: true default: true
}); });
if (update) { if (update) {
await updateItem('episodes', match.id, { plays }); await updateItem("episodes", match.id, { plays });
console.log(`✅ Updated episode: S${season_number}E${episode_number}`); console.log(`✅ Updated episode: S${season_number}E${episode_number}`);
} else { } else {
console.warn('⚠️ Skipped update.'); console.warn("⚠️ Skipped update.");
} }
} else { } else {
await createItem('episodes', { await createItem("episodes", {
season_number: Number(season_number), season_number: Number(season_number),
episode_number: Number(episode_number), episode_number: Number(episode_number),
plays: Number(plays), plays: Number(plays),

View file

@ -1,7 +1,7 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { loadConfig } from '../config.js'; import { loadConfig } from "../config.js";
import { initDirectusClient, searchItems, createItem } from '../directus/client.js'; import { initDirectusClient, searchItems, createItem } from "../directus/client.js";
import { removeUrlProtocol } from '../sanitize.js'; import { removeUrlProtocol } from "../sanitize.js";
export const addLinkToShare = async () => { export const addLinkToShare = async () => {
const config = await loadConfig(); const config = await loadConfig();
@ -10,34 +10,34 @@ export const addLinkToShare = async () => {
const { title, link, description, authorQuery } = await inquirer.prompt([ const { title, link, description, authorQuery } = await inquirer.prompt([
{ {
name: 'title', name: "title",
message: '📝 Title for the link:', message: "📝 Title for the link:",
validate: (input) => !!input || 'Title is required' validate: (input) => !!input || "Title is required"
}, },
{ {
name: 'link', name: "link",
message: '🔗 URL to share:', message: "🔗 URL to share:",
validate: (input) => input.startsWith('http') || 'Must be a valid URL' validate: (input) => input.startsWith("http") || "Must be a valid URL"
}, },
{ {
name: 'description', name: "description",
message: '🗒 Description (optional):', message: "🗒 Description (optional):",
default: '' default: ""
}, },
{ {
name: 'authorQuery', name: "authorQuery",
message: '👤 Search for an author:' message: "👤 Search for an author:"
} }
]); ]);
const authorMatches = await searchItems('authors', authorQuery); const authorMatches = await searchItems("authors", authorQuery);
let author; let author;
if (!authorMatches.length) { if (!authorMatches.length) {
const { shouldCreate } = await inquirer.prompt({ const { shouldCreate } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'shouldCreate', name: "shouldCreate",
message: '❌ No authors found. Do you want to create a new one?', message: "❌ No authors found. Do you want to create a new one?",
default: true default: true
}); });
@ -45,19 +45,19 @@ export const addLinkToShare = async () => {
const { name, url, mastodon, rss, json, newsletter, blogroll } = await inquirer.prompt([ const { name, url, mastodon, rss, json, newsletter, blogroll } = await inquirer.prompt([
{ {
name: 'name', name: "name",
message: '👤 Author name:', message: "👤 Author name:",
validate: (input) => !!input || 'Name is required' validate: (input) => !!input || "Name is required"
}, },
{ name: 'url', message: '🔗 URL (optional):', default: '' }, { name: "url", message: "🔗 URL (optional):", default: "" },
{ name: 'mastodon', message: '🐘 Mastodon handle (optional):', default: '' }, { name: "mastodon", message: "🐘 Mastodon handle (optional):", default: "" },
{ name: 'rss', message: '📡 RSS feed (optional):', default: '' }, { name: "rss", message: "📡 RSS feed (optional):", default: "" },
{ name: 'json', message: '🧾 JSON feed (optional):', default: '' }, { name: "json", message: "🧾 JSON feed (optional):", default: "" },
{ name: 'newsletter', message: '📰 Newsletter URL (optional):', default: '' }, { name: "newsletter", message: "📰 Newsletter URL (optional):", default: "" },
{ type: 'confirm', name: 'blogroll', message: '📌 Add to blogroll?', default: false } { type: "confirm", name: "blogroll", message: "📌 Add to blogroll?", default: false }
]); ]);
const created = await createItem('authors', { const created = await createItem("authors", {
name, name,
url, url,
mastodon, mastodon,
@ -70,9 +70,9 @@ export const addLinkToShare = async () => {
author = created.data?.id || created.id; author = created.data?.id || created.id;
} else { } else {
const response = await inquirer.prompt({ const response = await inquirer.prompt({
type: 'list', type: "list",
name: 'author', name: "author",
message: 'Select an author:', message: "Select an author:",
choices: authorMatches.map((a) => { choices: authorMatches.map((a) => {
const cleanUrl = removeUrlProtocol(a.url); const cleanUrl = removeUrlProtocol(a.url);
const display = cleanUrl ? `${a.name} (${cleanUrl})` : a.name; const display = cleanUrl ? `${a.name} (${cleanUrl})` : a.name;
@ -91,41 +91,64 @@ export const addLinkToShare = async () => {
while (true) { while (true) {
const { query } = await inquirer.prompt({ const { query } = await inquirer.prompt({
name: 'query', name: "query",
message: '🏷 Search for tags (or leave blank to finish):' message: "🏷 Search for tags (or leave blank to finish):"
}); });
const trimmedQuery = query.trim(); const trimmedQuery = query.trim();
if (!trimmedQuery) break; if (!trimmedQuery) break;
const tags = await searchItems('tags', trimmedQuery); const tags = await searchItems("tags", trimmedQuery);
if (!tags.length) { if (!tags.length) {
console.warn(`⚠️ No tags found matching "${trimmedQuery}"`); console.warn(`⚠️ No tags found matching "${trimmedQuery}"`);
const { shouldCreateTag } = await inquirer.prompt({
type: "confirm",
name: "shouldCreateTag",
message: `Do you want to create a new tag named "${trimmedQuery}"?`,
default: true
});
if (shouldCreateTag) {
const createdTag = await createItem("tags", { name: trimmedQuery });
const newTagId = createdTag.data?.id || createdTag.id;
tagIds.push(newTagId);
}
const { again } = await inquirer.prompt({
type: "confirm",
name: "again",
message: "Search and select more tags?",
default: false
});
if (!again) break;
continue; continue;
} }
const { selected } = await inquirer.prompt({ const { selected } = await inquirer.prompt({
type: 'checkbox', type: "checkbox",
name: 'selected', name: "selected",
message: '✔ Select tags to add:', message: "✔ Select tags to add:",
choices: tags.map((tag) => ({ name: tag.name, value: tag.id })) choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
}); });
tagIds.push(...selected); tagIds.push(...selected);
const { again } = await inquirer.prompt({ const { again } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'again', name: "again",
message: 'Search and select more tags?', message: "Search and select more tags?",
default: false default: false
}); });
if (!again) break; if (!again) break;
} }
await createItem('links', { await createItem("links", {
title, title,
link, link,
description, description,
@ -134,5 +157,5 @@ export const addLinkToShare = async () => {
date: new Date().toISOString() date: new Date().toISOString()
}); });
console.log('✅ Link created successfully.'); console.log("✅ Link created successfully.");
}; };

View file

@ -1,16 +1,16 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { loadConfig } from '../config.js'; import { loadConfig } from "../config.js";
import { initDirectusClient, createItem, searchItems } from '../directus/client.js'; import { initDirectusClient, createItem, searchItems } from "../directus/client.js";
import { promptForMultipleRelations } from '../directus/relationHelpers.js'; import { promptForMultipleRelations } from "../directus/relationHelpers.js";
const ASSOCIATED_MEDIA_TYPES = ['artists', 'books', 'movies', 'shows', 'genres']; const ASSOCIATED_MEDIA_TYPES = ["artists", "books", "movies", "shows", "genres"];
const BLOCK_COLLECTIONS = [ const BLOCK_COLLECTIONS = [
'youtube_player', "youtube_player",
'github_banner', "github_banner",
'npm_banner', "npm_banner",
'rss_banner', "rss_banner",
'calendar_banner', "calendar_banner",
'forgejo_banner' "forgejo_banner"
]; ];
export const addPost = async () => { export const addPost = async () => {
@ -20,24 +20,24 @@ export const addPost = async () => {
const { title, description, content, featured } = await inquirer.prompt([ const { title, description, content, featured } = await inquirer.prompt([
{ {
name: 'title', name: "title",
message: '📝 Title:', message: "📝 Title:",
validate: (input) => !!input || 'Title is required' validate: (input) => !!input || "Title is required"
}, },
{ {
name: 'description', name: "description",
message: '🗒 Description:', message: "🗒 Description:",
default: '' default: ""
}, },
{ {
name: 'content', name: "content",
message: '📄 Content:', message: "📄 Content:",
default: '' default: ""
}, },
{ {
type: 'confirm', type: "confirm",
name: 'featured', name: "featured",
message: '⭐ Featured?', message: "⭐ Featured?",
default: false default: false
} }
]); ]);
@ -46,14 +46,14 @@ export const addPost = async () => {
while (true) { while (true) {
const { query } = await inquirer.prompt({ const { query } = await inquirer.prompt({
name: 'query', name: "query",
message: '🏷 Search for tags (or leave blank to finish):' message: "🏷 Search for tags (or leave blank to finish):"
}); });
const trimmedQuery = query.trim(); const trimmedQuery = query.trim();
if (!trimmedQuery) break; if (!trimmedQuery) break;
const tags = await searchItems('tags', trimmedQuery); const tags = await searchItems("tags", trimmedQuery);
if (!tags.length) { if (!tags.length) {
console.warn(`⚠️ No tags found matching "${trimmedQuery}"`); console.warn(`⚠️ No tags found matching "${trimmedQuery}"`);
@ -62,18 +62,18 @@ export const addPost = async () => {
} }
const { selected } = await inquirer.prompt({ const { selected } = await inquirer.prompt({
type: 'checkbox', type: "checkbox",
name: 'selected', name: "selected",
message: '✔ Select tags to add:', message: "✔ Select tags to add:",
choices: tags.map((tag) => ({ name: tag.name, value: tag.id })) choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
}); });
tagIds.push(...selected); tagIds.push(...selected);
const { again } = await inquirer.prompt({ const { again } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'again', name: "again",
message: 'Search and select more tags?', message: "Search and select more tags?",
default: false default: false
}); });
@ -82,25 +82,25 @@ export const addPost = async () => {
const selectedBlocks = []; const selectedBlocks = [];
const { includeBlocks } = await inquirer.prompt({ const { includeBlocks } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'includeBlocks', name: "includeBlocks",
message: ' Add blocks?', message: " Add blocks?",
default: false default: false
}); });
if (includeBlocks) { if (includeBlocks) {
while (true) { while (true) {
const { collection } = await inquirer.prompt({ const { collection } = await inquirer.prompt({
type: 'list', type: "list",
name: 'collection', name: "collection",
message: '🧱 Choose a block collection (or Cancel to finish):', message: "🧱 Choose a block collection (or Cancel to finish):",
choices: [...BLOCK_COLLECTIONS, new inquirer.Separator(), 'Cancel'] choices: [...BLOCK_COLLECTIONS, new inquirer.Separator(), "Cancel"]
}); });
if (collection === 'Cancel') break; if (collection === "Cancel") break;
const { query } = await inquirer.prompt({ const { query } = await inquirer.prompt({
name: 'query', name: "query",
message: `🔍 Search ${collection}:` message: `🔍 Search ${collection}:`
}); });
const results = await searchItems(collection, query); const results = await searchItems(collection, query);
@ -112,8 +112,8 @@ export const addPost = async () => {
} }
const { itemId } = await inquirer.prompt({ const { itemId } = await inquirer.prompt({
type: 'list', type: "list",
name: 'itemId', name: "itemId",
message: `Select an item from ${collection}:`, message: `Select an item from ${collection}:`,
choices: results.map((item) => ({ choices: results.map((item) => ({
name: item.title || item.name || item.id, name: item.title || item.name || item.id,
@ -124,9 +124,9 @@ export const addPost = async () => {
selectedBlocks.push({ collection, item: itemId }); selectedBlocks.push({ collection, item: itemId });
const { again } = await inquirer.prompt({ const { again } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'again', name: "again",
message: ' Add another block?', message: " Add another block?",
default: false default: false
}); });
@ -136,16 +136,16 @@ export const addPost = async () => {
const associatedMediaPayload = {}; const associatedMediaPayload = {};
const { includeMedia } = await inquirer.prompt({ const { includeMedia } = await inquirer.prompt({
type: 'confirm', type: "confirm",
name: 'includeMedia', name: "includeMedia",
message: ' Add associated media?', message: " Add associated media?",
default: false default: false
}); });
if (includeMedia) { if (includeMedia) {
for (const mediaType of ASSOCIATED_MEDIA_TYPES) { for (const mediaType of ASSOCIATED_MEDIA_TYPES) {
const { query } = await inquirer.prompt({ const { query } = await inquirer.prompt({
name: 'query', name: "query",
message: `🔎 Search for ${mediaType} to associate (or leave blank to skip):` message: `🔎 Search for ${mediaType} to associate (or leave blank to skip):`
}); });
@ -160,8 +160,8 @@ export const addPost = async () => {
} }
const { selected } = await inquirer.prompt({ const { selected } = await inquirer.prompt({
type: 'checkbox', type: "checkbox",
name: 'selected', name: "selected",
message: `✔ Select ${mediaType} to associate:`, message: `✔ Select ${mediaType} to associate:`,
choices: matches.map((m) => ({ choices: matches.map((m) => ({
name: m.name_string || m.title || m.name || m.label || m.id, name: m.name_string || m.title || m.name || m.label || m.id,
@ -176,7 +176,7 @@ export const addPost = async () => {
} }
} }
const media = await promptForMultipleRelations('media', 'Associated media'); const media = await promptForMultipleRelations("media", "Associated media");
const payload = { const payload = {
title, title,
description, description,
@ -188,7 +188,7 @@ export const addPost = async () => {
...associatedMediaPayload ...associatedMediaPayload
}; };
await createItem('posts', payload); await createItem("posts", payload);
console.log('✅ Post created successfully.'); console.log("✅ Post created successfully.");
}; };

View file

@ -1,24 +1,24 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { addPost } from './addPost.js'; import { addPost } from "./addPost.js";
import { addLinkToShare } from './addLinkToShare.js'; import { addLinkToShare } from "./addLinkToShare.js";
import { addEpisodeToShow } from './addEpisodeToShow.js'; import { addEpisodeToShow } from "./addEpisodeToShow.js";
import { updateReadingProgress } from './updateReadingProgress.js'; import { updateReadingProgress } from "./updateReadingProgress.js";
import { addBlockedRobot } from './addBlockedRobot.js'; import { addBlockedRobot } from "./addBlockedRobot.js";
const TASKS = [ const TASKS = [
{ name: '📄 Add post', handler: addPost }, { name: "📄 Add post", handler: addPost },
{ name: '🔗 Add link to share', handler: addLinkToShare }, { name: "🔗 Add link to share", handler: addLinkToShare },
{ name: ' Add episode to show', handler: addEpisodeToShow }, { name: " Add episode to show", handler: addEpisodeToShow },
{ name: '📚 Update reading progress', handler: updateReadingProgress }, { name: "📚 Update reading progress", handler: updateReadingProgress },
{ name: '🤖 Block robot', handler: addBlockedRobot } { name: "🤖 Block robot", handler: addBlockedRobot }
]; ];
export const runTasksMenu = async () => { export const runTasksMenu = async () => {
const { task } = await inquirer.prompt([ const { task } = await inquirer.prompt([
{ {
type: 'list', type: "list",
name: 'task', name: "task",
message: 'Select a task to perform:', message: "Select a task to perform:",
choices: TASKS.map((t) => ({ name: t.name, value: t.handler })) choices: TASKS.map((t) => ({ name: t.name, value: t.handler }))
} }
]); ]);

View file

@ -1,13 +1,13 @@
import inquirer from 'inquirer'; import inquirer from "inquirer";
import { loadConfig } from '../config.js'; import { loadConfig } from "../config.js";
import { initDirectusClient, searchItems, updateItem } from '../directus/client.js'; import { initDirectusClient, searchItems, updateItem } from "../directus/client.js";
export const updateReadingProgress = async () => { export const updateReadingProgress = async () => {
const config = await loadConfig(); const config = await loadConfig();
initDirectusClient(config); initDirectusClient(config);
const readingBooks = await searchItems('books', '', { read_status: 'started' }); const readingBooks = await searchItems("books", "", { read_status: "started" });
if (!readingBooks.length) { if (!readingBooks.length) {
console.log('📖 No books currently marked as "started".'); console.log('📖 No books currently marked as "started".');
@ -16,9 +16,9 @@ export const updateReadingProgress = async () => {
} }
const { bookId } = await inquirer.prompt({ const { bookId } = await inquirer.prompt({
type: 'list', type: "list",
name: 'bookId', name: "bookId",
message: '📚 Select a book to update progress:', message: "📚 Select a book to update progress:",
choices: readingBooks.map((book) => { choices: readingBooks.map((book) => {
const title = book.title || book.name || `Book #${book.id}`; const title = book.title || book.name || `Book #${book.id}`;
const progress = book.progress ?? 0; const progress = book.progress ?? 0;
@ -30,16 +30,16 @@ export const updateReadingProgress = async () => {
}) })
}); });
const { progress } = await inquirer.prompt({ const { progress } = await inquirer.prompt({
name: 'progress', name: "progress",
message: '📕 New progress percentage (0100):', message: "📕 New progress percentage (0100):",
validate: (input) => { validate: (input) => {
const num = Number(input); const num = Number(input);
return (!isNaN(num) && num >= 0 && num <= 100) || 'Enter a number from 0 to 100'; return (!isNaN(num) && num >= 0 && num <= 100) || "Enter a number from 0 to 100";
} }
}); });
await updateItem('books', bookId, { progress: Number(progress) }); await updateItem("books", bookId, { progress: Number(progress) });
console.log(`✅ Updated book progress to ${progress}%`); console.log(`✅ Updated book progress to ${progress}%`);
}; };

4
cli/package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "coryd", "name": "coryd",
"version": "3.2.6", "version": "3.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coryd", "name": "coryd",
"version": "3.2.6", "version": "3.3.0",
"dependencies": { "dependencies": {
"@directus/sdk": "^19.1.0", "@directus/sdk": "^19.1.0",
"chalk": "^5.4.1", "chalk": "^5.4.1",

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd", "name": "coryd",
"version": "3.2.6", "version": "3.3.0",
"description": "The CLI for my site to handle tasks, run commands or jobs and download things.", "description": "The CLI for my site to handle tasks, run commands or jobs and download things.",
"type": "module", "type": "module",
"bin": { "bin": {

View file

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

View file

@ -1,9 +1,9 @@
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import { minify } from 'terser'; import { minify } from "terser";
export const minifyJsComponents = async () => { export const minifyJsComponents = async () => {
const scriptsDir = 'dist/assets/scripts'; const scriptsDir = "dist/assets/scripts";
const minifyJsFilesInDir = async (dir) => { const minifyJsFilesInDir = async (dir) => {
const files = fs.readdirSync(dir); const files = fs.readdirSync(dir);
@ -13,8 +13,8 @@ export const minifyJsComponents = async () => {
if (stat.isDirectory()) { if (stat.isDirectory()) {
await minifyJsFilesInDir(filePath); await minifyJsFilesInDir(filePath);
} else if (fileName.endsWith('.js')) { } else if (fileName.endsWith(".js")) {
const fileContent = fs.readFileSync(filePath, 'utf8'); const fileContent = fs.readFileSync(filePath, "utf8");
const minified = await minify(fileContent); const minified = await minify(fileContent);
if (minified.error) { if (minified.error) {

View file

@ -1,4 +1,4 @@
import * as cheerio from 'cheerio'; import * as cheerio from "cheerio";
export default { export default {
convertRelativeLinks: (htmlContent, domain) => { convertRelativeLinks: (htmlContent, domain) => {
@ -6,32 +6,32 @@ export default {
const $ = cheerio.load(htmlContent); const $ = cheerio.load(htmlContent);
$('a[href]').each((_, link) => { $("a[href]").each((_, link) => {
const $link = $(link); const $link = $(link);
let href = $link.attr('href'); let href = $link.attr("href");
if (href.startsWith('#')) { if (href.startsWith("#")) {
$link.replaceWith($('<span>').text($link.text())); $link.replaceWith($("<span>").text($link.text()));
} else if (!href.startsWith('http://') && !href.startsWith('https://')) { } else if (!href.startsWith("http://") && !href.startsWith("https://")) {
const normalizedDomain = domain.replace(/\/$/, ''); const normalizedDomain = domain.replace(/\/$/, "");
const normalizedHref = href.replace(/^\/+/, ''); const normalizedHref = href.replace(/^\/+/, "");
$link.attr('href', `${normalizedDomain}/${normalizedHref}`); $link.attr("href", `${normalizedDomain}/${normalizedHref}`);
} }
}); });
return $.html(); return $.html();
}, },
generatePermalink: (url, baseUrl) => { generatePermalink: (url, baseUrl) => {
if (url?.includes('http') || !baseUrl) return url; if (url?.includes("http") || !baseUrl) return url;
return `${baseUrl}${url}`; return `${baseUrl}${url}`;
}, },
getRemoteFileSize: async (url) => { getRemoteFileSize: async (url) => {
try { try {
const response = await fetch(url, { method: 'HEAD' }); const response = await fetch(url, { method: "HEAD" });
if (!response.ok) return 0; if (!response.ok) return 0;
const contentLength = response.headers.get('content-length'); const contentLength = response.headers.get("content-length");
if (!contentLength) return 0; if (!contentLength) return 0;

View file

@ -1,22 +1,22 @@
import truncateHtml from 'truncate-html'; import truncateHtml from "truncate-html";
export default { export default {
encodeAmp: (string) => { encodeAmp: (string) => {
if (!string) return; if (!string) return;
const pattern = /&(?!(?:[a-zA-Z]+|#[0-9]+|#x[0-9a-fA-F]+);)/g; const pattern = /&(?!(?:[a-zA-Z]+|#[0-9]+|#x[0-9a-fA-F]+);)/g;
const replacement = '&amp;'; const replacement = "&amp;";
return string.replace(pattern, replacement); return string.replace(pattern, replacement);
}, },
replaceQuotes: (string) => { replaceQuotes: (string) => {
if (!string) return ''; if (!string) return "";
return string.replace(/"/g, '&quot;'); return string.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
}, },
htmlTruncate: (content, limit = 50) => htmlTruncate: (content, limit = 50) =>
truncateHtml(content, limit, { truncateHtml(content, limit, {
byWords: true, byWords: true,
ellipsis: '...' ellipsis: "..."
}), }),
shuffleArray: (array) => { shuffleArray: (array) => {
const shuffled = [...array]; const shuffled = [...array];
@ -32,12 +32,12 @@ export default {
}, },
mergeArray: (a, b) => (Array.isArray(a) && Array.isArray(b) ? [...new Set([...a, ...b])] : []), mergeArray: (a, b) => (Array.isArray(a) && Array.isArray(b) ? [...new Set([...a, ...b])] : []),
pluralize: (count, string, trailing) => { pluralize: (count, string, trailing) => {
const countStr = String(count).replace(/,/g, ''); const countStr = String(count).replace(/,/g, "");
if (parseInt(countStr, 10) === 1) return string; if (parseInt(countStr, 10) === 1) return string;
return `${string}s${trailing ? `${trailing}` : ''}`; return `${string}s${trailing ? `${trailing}` : ""}`;
}, },
jsonEscape: (string) => JSON.stringify(string), jsonEscape: (string) => JSON.stringify(string),
regexEscape: (string) => string.replace(/[.*+?^${}()[]\\]/g, '\\$&') regexEscape: (string) => string.replace(/[.*+?^${}()[]\\]/g, "\\$&")
}; };

View file

@ -1,8 +1,8 @@
import feeds from './feeds.js'; import feeds from "./feeds.js";
import general from './general.js'; import general from "./general.js";
import media from './media.js'; import media from "./media.js";
import metadata from './metadata.js'; import metadata from "./metadata.js";
import navigation from './navigation.js'; import navigation from "./navigation.js";
export default { export default {
...feeds, ...feeds,

View file

@ -7,12 +7,12 @@ export default {
.map( .map(
(year, index) => (year, index) =>
`<a href="/reading/years/${year.value}">${year.value}</a>${ `<a href="/reading/years/${year.value}">${year.value}</a>${
index < years.length - 1 ? ' • ' : '' index < years.length - 1 ? " • " : ""
}` }`
) )
.join(''), .join(""),
mediaLinks: (data, type, count = 10) => { mediaLinks: (data, type, count = 10) => {
if (!data || !type) return ''; if (!data || !type) return "";
const dataSlice = data.slice(0, count); const dataSlice = data.slice(0, count);
@ -20,21 +20,21 @@ export default {
const buildLink = (item) => { const buildLink = (item) => {
switch (type) { switch (type) {
case 'genre': case "genre":
return `<a href="${item.genre_url}">${item.genre_name}</a>`; return `<a href="${item.genre_url}">${item.genre_name}</a>`;
case 'artist': case "artist":
return `<a href="${item.url}">${item.name}</a>`; return `<a href="${item.url}">${item.name}</a>`;
case 'book': case "book":
return `<a href="${item.url}">${item.title}</a>`; return `<a href="${item.url}">${item.title}</a>`;
default: default:
return ''; return "";
} }
}; };
if (dataSlice.length === 1) return buildLink(dataSlice[0]); if (dataSlice.length === 1) return buildLink(dataSlice[0]);
const links = dataSlice.map(buildLink); const links = dataSlice.map(buildLink);
const allButLast = links.slice(0, -1).join(', '); const allButLast = links.slice(0, -1).join(", ");
const last = links[links.length - 1]; const last = links[links.length - 1];
return `${allButLast} and ${last}`; return `${allButLast} and ${last}`;

View file

@ -1,8 +1,8 @@
export default { export default {
getMetadata: (data = {}, globals = {}, page = {}, title = '', description = '', schema = '') => { getMetadata: (data = {}, globals = {}, page = {}, title = "", description = "", schema = "") => {
const metadata = data?.metadata; const metadata = data?.metadata;
const baseUrl = globals.url || ''; const baseUrl = globals.url || "";
const ogPath = '/og/w800'; const ogPath = "/og/w800";
const image = const image =
metadata?.open_graph_image || globals.metadata?.open_graph_image || globals.avatar; metadata?.open_graph_image || globals.metadata?.open_graph_image || globals.avatar;
const rawTitle = title || page.title || metadata?.title || globals.site_name; const rawTitle = title || page.title || metadata?.title || globals.site_name;
@ -10,7 +10,7 @@ export default {
rawTitle === globals.site_name ? globals.site_name : `${rawTitle}${globals.site_name}`; rawTitle === globals.site_name ? globals.site_name : `${rawTitle}${globals.site_name}`;
const resolvedDescription = const resolvedDescription =
description || metadata?.description || page.description || globals.site_description; description || metadata?.description || page.description || globals.site_description;
const url = metadata?.url || (page.url ? `${baseUrl}${page.url}` : '#'); const url = metadata?.url || (page.url ? `${baseUrl}${page.url}` : "#");
return { return {
title: resolvedTitle, title: resolvedTitle,

View file

@ -1,11 +1,11 @@
const normalizeUrl = (url) => const normalizeUrl = (url) =>
url.replace(/index\.php$|index\.html$/i, '').replace(/\.php$|\.html$/i, '') || '/'; url.replace(/index\.php$|index\.html$/i, "").replace(/\.php$|\.html$/i, "") || "/";
export default { export default {
isLinkActive: (category, page) => { isLinkActive: (category, page) => {
const normalized = normalizeUrl(page); const normalized = normalizeUrl(page);
return ( return (
normalized.includes(category) && normalized.split('/').filter((a) => a !== '').length <= 1 normalized.includes(category) && normalized.split("/").filter((a) => a !== "").length <= 1
); );
}, },
normalizeUrl normalizeUrl

View file

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

View file

@ -1,8 +1,8 @@
import htmlmin from 'html-minifier-terser'; import htmlmin from "html-minifier-terser";
export const htmlConfig = (eleventyConfig) => { export const htmlConfig = (eleventyConfig) => {
eleventyConfig.addTransform('html-minify', (content, path) => { eleventyConfig.addTransform("html-minify", (content, path) => {
if (path && path.endsWith('.html')) { if (path && path.endsWith(".html")) {
return htmlmin.minify(content, { return htmlmin.minify(content, {
collapseBooleanAttributes: true, collapseBooleanAttributes: true,
collapseWhitespace: true, collapseWhitespace: true,

View file

@ -1,5 +1,5 @@
import { cssConfig } from './css-config.js'; import { cssConfig } from "./css-config.js";
import { htmlConfig } from './html-config.js'; import { htmlConfig } from "./html-config.js";
import { markdownLib } from './markdown.js'; import { markdownLib } from "./markdown.js";
export default { cssConfig, htmlConfig, markdownLib }; export default { cssConfig, htmlConfig, markdownLib };

View file

@ -1,8 +1,8 @@
import markdownIt from 'markdown-it'; import markdownIt from "markdown-it";
import markdownItAnchor from 'markdown-it-anchor'; import markdownItAnchor from "markdown-it-anchor";
import markdownItFootnote from 'markdown-it-footnote'; import markdownItFootnote from "markdown-it-footnote";
import markdownItLinkAttributes from 'markdown-it-link-attributes'; import markdownItLinkAttributes from "markdown-it-link-attributes";
import markdownItPrism from 'markdown-it-prism'; import markdownItPrism from "markdown-it-prism";
export const markdownLib = markdownIt({ html: true, linkify: true }) export const markdownLib = markdownIt({ html: true, linkify: true })
.use(markdownItAnchor, { .use(markdownItAnchor, {
@ -17,7 +17,7 @@ export const markdownLib = markdownIt({ html: true, linkify: true })
return href.match(/^https?:\/\//); return href.match(/^https?:\/\//);
}, },
attrs: { attrs: {
rel: 'noopener' rel: "noopener"
} }
} }
]) ])

View file

@ -1,13 +1,13 @@
import { createRequire } from 'module'; import { createRequire } from "module";
import 'dotenv/config'; import "dotenv/config";
import filters from './config/filters/index.js'; import filters from "./config/filters/index.js";
import tablerIcons from '@cdransf/eleventy-plugin-tabler-icons'; import tablerIcons from "@cdransf/eleventy-plugin-tabler-icons";
import { minifyJsComponents } from './config/events/minify-js.js'; import { minifyJsComponents } from "./config/events/minify-js.js";
import { albumReleasesCalendar } from './config/collections/index.js'; import { albumReleasesCalendar } from "./config/collections/index.js";
import plugins from './config/plugins/index.js'; import plugins from "./config/plugins/index.js";
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const appVersion = require('./package.json').version; const appVersion = require("./package.json").version;
export default async function (eleventyConfig) { export default async function (eleventyConfig) {
eleventyConfig.addPlugin(tablerIcons); eleventyConfig.addPlugin(tablerIcons);
@ -18,22 +18,22 @@ export default async function (eleventyConfig) {
eleventyConfig.configureErrorReporting({ allowMissingExtensions: true }); eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });
eleventyConfig.setLiquidOptions({ jsTruthy: true }); eleventyConfig.setLiquidOptions({ jsTruthy: true });
eleventyConfig.watchIgnores.add('queries/**'); eleventyConfig.watchIgnores.add("queries/**");
eleventyConfig.addPassthroughCopy('src/assets'); eleventyConfig.addPassthroughCopy("src/assets");
eleventyConfig.addPassthroughCopy('api'); eleventyConfig.addPassthroughCopy("api");
eleventyConfig.addPassthroughCopy('bootstrap.php'); eleventyConfig.addPassthroughCopy("bootstrap.php");
eleventyConfig.addPassthroughCopy({ eleventyConfig.addPassthroughCopy({
'node_modules/minisearch/dist/umd/index.js': 'assets/scripts/components/minisearch.js', "node_modules/minisearch/dist/umd/index.js": "assets/scripts/components/minisearch.js",
'node_modules/youtube-video-element/dist/youtube-video-element.js': "node_modules/youtube-video-element/dist/youtube-video-element.js":
'assets/scripts/components/youtube-video-element.js' "assets/scripts/components/youtube-video-element.js"
}); });
eleventyConfig.addCollection('albumReleasesCalendar', albumReleasesCalendar); eleventyConfig.addCollection("albumReleasesCalendar", albumReleasesCalendar);
eleventyConfig.setLibrary('md', plugins.markdownLib); eleventyConfig.setLibrary("md", plugins.markdownLib);
eleventyConfig.addLiquidFilter('markdown', (content) => { eleventyConfig.addLiquidFilter("markdown", (content) => {
if (!content) return; if (!content) return;
return plugins.markdownLib.render(content); return plugins.markdownLib.render(content);
}); });
@ -42,17 +42,17 @@ export default async function (eleventyConfig) {
eleventyConfig.addLiquidFilter(filterName, filters[filterName]); eleventyConfig.addLiquidFilter(filterName, filters[filterName]);
}); });
eleventyConfig.addShortcode('appVersion', () => appVersion); eleventyConfig.addShortcode("appVersion", () => appVersion);
eleventyConfig.on('afterBuild', minifyJsComponents); eleventyConfig.on("afterBuild", minifyJsComponents);
return { return {
dir: { dir: {
input: 'src', input: "src",
includes: 'includes', includes: "includes",
layouts: 'layouts', layouts: "layouts",
data: 'data', data: "data",
output: 'dist' output: "dist"
} }
}; };
} }

86
package-lock.json generated
View file

@ -16,6 +16,7 @@
"@11ty/eleventy": "3.1.1", "@11ty/eleventy": "3.1.1",
"@11ty/eleventy-fetch": "5.1.0", "@11ty/eleventy-fetch": "5.1.0",
"@cdransf/eleventy-plugin-tabler-icons": "^2.13.1", "@cdransf/eleventy-plugin-tabler-icons": "^2.13.1",
"@shopify/prettier-plugin-liquid": "1.9.3",
"cheerio": "1.1.0", "cheerio": "1.1.0",
"concurrently": "9.1.2", "concurrently": "9.1.2",
"cssnano": "^7.0.7", "cssnano": "^7.0.7",
@ -235,9 +236,9 @@
} }
}, },
"node_modules/@11ty/recursive-copy": { "node_modules/@11ty/recursive-copy": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.2.tgz",
"integrity": "sha512-Zsg1xgfdVTMKNPj9o4FZeYa73dFZRX856CL4LsmqPMvDr0TuIK4cH9CVWJyf0OkNmM8GmlibGX18fF0B75Rn1w==", "integrity": "sha512-174nFXxL/6KcYbLYpra+q3nDbfKxLxRTNVY1atq2M1pYYiPfHse++3IFNl8mjPFsd7y2qQjxLORzIjHMjL3NDQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -466,6 +467,31 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@shopify/liquid-html-parser": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@shopify/liquid-html-parser/-/liquid-html-parser-2.8.2.tgz",
"integrity": "sha512-g8DRcz4wUj4Ttxm+rK1qPuvIV2/ZqlyGRcVytVMbUkrr/+eVL2yQI/jRGDMeOamkRqB3InuoOjF7nARH+o9UYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"line-column": "^1.0.2",
"ohm-js": "^16.3.0"
}
},
"node_modules/@shopify/prettier-plugin-liquid": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@shopify/prettier-plugin-liquid/-/prettier-plugin-liquid-1.9.3.tgz",
"integrity": "sha512-XRRnwfONrzjW8AY/l39szH9OgCCg5Xx61QxxdrC3BT2RAqo229jomjhCEszGIUJ5YZYq1ewdyBwbvUVTUSTcTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shopify/liquid-html-parser": "^2.8.2",
"html-styles": "^1.0.0"
},
"peerDependencies": {
"prettier": "^2.0.0 || ^3.0.0"
}
},
"node_modules/@sindresorhus/slugify": { "node_modules/@sindresorhus/slugify": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
@ -1655,9 +1681,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.167", "version": "1.5.168",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.168.tgz",
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", "integrity": "sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -2150,6 +2176,13 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/html-styles": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/html-styles/-/html-styles-1.0.0.tgz",
"integrity": "sha512-cDl5dcj73oI4Hy0DSUNh54CAwslNLJRCCoO+RNkVo+sBrjA/0+7E/xzvj3zH/GxbbBLGJhE0hBe1eg+0FINC6w==",
"dev": true,
"license": "MIT"
},
"node_modules/htmlparser2": { "node_modules/htmlparser2": {
"version": "10.0.0", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
@ -2405,6 +2438,13 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT"
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -2422,6 +2462,19 @@
"node": ">=6.0" "node": ">=6.0"
} }
}, },
"node_modules/isobject": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
"integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
"dev": true,
"license": "MIT",
"dependencies": {
"isarray": "1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
@ -2538,6 +2591,17 @@
"url": "https://github.com/sponsors/antonk52" "url": "https://github.com/sponsors/antonk52"
} }
}, },
"node_modules/line-column": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz",
"integrity": "sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==",
"dev": true,
"license": "MIT",
"dependencies": {
"isarray": "^1.0.0",
"isobject": "^2.0.0"
}
},
"node_modules/linkify-it": { "node_modules/linkify-it": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
@ -3769,6 +3833,16 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/ohm-js": {
"version": "16.6.0",
"resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-16.6.0.tgz",
"integrity": "sha512-X9P4koSGa7swgVQ0gt71UCYtkAQGOjciJPJAz74kDxWt8nXbH5HrDOQG6qBDH7SR40ktNv4x61BwpTDE9q4lRA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.1"
}
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "10.6.7", "version": "10.7.0",
"description": "The source for my personal site. Built using 11ty (and other tools).", "description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module", "type": "module",
"engines": { "engines": {
@ -13,7 +13,7 @@
"php": "export $(grep -v '^#' .env | xargs) && php -d error_reporting=E_ALL^E_DEPRECATED -S localhost:8080 -t dist", "php": "export $(grep -v '^#' .env | xargs) && php -d error_reporting=E_ALL^E_DEPRECATED -S localhost:8080 -t dist",
"build": "eleventy", "build": "eleventy",
"clean": "rimraf dist .cache", "clean": "rimraf dist .cache",
"format": "npx prettier --write '**/*.{js,json,css,md}' && composer format:php && npm run format:sql", "format": "npx prettier --write '**/*.{js,json,css,md,liquid}' && composer format:php && npm run format:sql",
"format:sql": "find queries -name '*.sql' -print0 | xargs -0 -n 1 sql-formatter --language postgresql --fix", "format:sql": "find queries -name '*.sql' -print0 | xargs -0 -n 1 sql-formatter --language postgresql --fix",
"lint:md": "markdownlint '**/*.md'", "lint:md": "markdownlint '**/*.md'",
"update": "composer update && npm upgrade && npm --prefix cli upgrade && ncu && ncu --cwd cli", "update": "composer update && npm upgrade && npm --prefix cli upgrade && ncu && ncu --cwd cli",
@ -22,7 +22,7 @@
"prepare": "husky" "prepare": "husky"
}, },
"lint-staged": { "lint-staged": {
"*.{js,json,css,md}": "prettier --write", "*.{js,json,css,md,liquid}": "prettier --write",
"*.php": "composer format:php", "*.php": "composer format:php",
"*.md": "markdownlint" "*.md": "markdownlint"
}, },
@ -45,6 +45,7 @@
"@11ty/eleventy": "3.1.1", "@11ty/eleventy": "3.1.1",
"@11ty/eleventy-fetch": "5.1.0", "@11ty/eleventy-fetch": "5.1.0",
"@cdransf/eleventy-plugin-tabler-icons": "^2.13.1", "@cdransf/eleventy-plugin-tabler-icons": "^2.13.1",
"@shopify/prettier-plugin-liquid": "1.9.3",
"cheerio": "1.1.0", "cheerio": "1.1.0",
"concurrently": "9.1.2", "concurrently": "9.1.2",
"cssnano": "^7.0.7", "cssnano": "^7.0.7",

View file

@ -1,31 +1,31 @@
class SelectPagination extends HTMLElement { class SelectPagination extends HTMLElement {
static register(tagName = 'select-pagination') { static register(tagName = "select-pagination") {
if ('customElements' in window) customElements.define(tagName, this); if ("customElements" in window) customElements.define(tagName, this);
} }
static get observedAttributes() { static get observedAttributes() {
return ['data-base-index']; return ["data-base-index"];
} }
get baseIndex() { get baseIndex() {
return parseInt(this.getAttribute('data-base-index') || '0', 10); return parseInt(this.getAttribute("data-base-index") || "0", 10);
} }
connectedCallback() { connectedCallback() {
if (this.shadowRoot) return; if (this.shadowRoot) return;
this.attachShadow({ mode: 'open' }).appendChild(document.createElement('slot')); this.attachShadow({ mode: "open" }).appendChild(document.createElement("slot"));
const uriSegments = window.location.pathname.split('/').filter(Boolean); const uriSegments = window.location.pathname.split("/").filter(Boolean);
let pageNumber = this.extractPageNumber(uriSegments); let pageNumber = this.extractPageNumber(uriSegments);
if (pageNumber === null) pageNumber = this.baseIndex; if (pageNumber === null) pageNumber = this.baseIndex;
this.control = this.querySelector('select'); this.control = this.querySelector("select");
this.control.value = pageNumber.toString(); this.control.value = pageNumber.toString();
this.control.addEventListener('change', (event) => { this.control.addEventListener("change", (event) => {
pageNumber = parseInt(event.target.value); pageNumber = parseInt(event.target.value);
const updatedUrlSegments = this.updateUrlSegments(uriSegments, pageNumber); const updatedUrlSegments = this.updateUrlSegments(uriSegments, pageNumber);
window.location.href = `${window.location.origin}/${updatedUrlSegments.join('/')}`; window.location.href = `${window.location.origin}/${updatedUrlSegments.join("/")}`;
}); });
} }

View file

@ -1,66 +1,66 @@
window.addEventListener('load', () => { window.addEventListener("load", () => {
// service worker // service worker
if ('serviceWorker' in navigator) navigator.serviceWorker.register('/assets/scripts/sw.js'); if ("serviceWorker" in navigator) navigator.serviceWorker.register("/assets/scripts/sw.js");
// dialog controls // dialog controls
(() => { (() => {
const dialogButtons = document.querySelectorAll('.dialog-open'); const dialogButtons = document.querySelectorAll(".dialog-open");
if (!dialogButtons.length) return; if (!dialogButtons.length) return;
dialogButtons.forEach((button) => { dialogButtons.forEach((button) => {
const dialogId = button.getAttribute('data-dialog-trigger'); const dialogId = button.getAttribute("data-dialog-trigger");
const dialog = document.getElementById(`dialog-${dialogId}`); const dialog = document.getElementById(`dialog-${dialogId}`);
if (!dialog) return; if (!dialog) return;
const closeButton = dialog.querySelector('.dialog-close'); const closeButton = dialog.querySelector(".dialog-close");
button.addEventListener('click', async () => { button.addEventListener("click", async () => {
const isDynamic = dialog.dataset.dynamic; const isDynamic = dialog.dataset.dynamic;
const isLoaded = dialog.dataset.loaded; const isLoaded = dialog.dataset.loaded;
if (isDynamic && !isLoaded) { if (isDynamic && !isLoaded) {
const markdownFields = dialog.dataset.markdown || ''; const markdownFields = dialog.dataset.markdown || "";
try { try {
const res = await fetch( const res = await fetch(
`/api/query.php?data=${isDynamic}&id=${dialogId}&markdown=${encodeURIComponent(markdownFields)}` `/api/query.php?data=${isDynamic}&id=${dialogId}&markdown=${encodeURIComponent(markdownFields)}`
); );
const [data] = await res.json(); const [data] = await res.json();
const firstField = markdownFields.split(',')[0]?.trim(); const firstField = markdownFields.split(",")[0]?.trim();
const html = data?.[`${firstField}_html`] || '<p>No notes available.</p>'; const html = data?.[`${firstField}_html`] || "<p>No notes available.</p>";
dialog.querySelectorAll('.dialog-dynamic').forEach((el) => el.remove()); dialog.querySelectorAll(".dialog-dynamic").forEach((el) => el.remove());
const container = document.createElement('div'); const container = document.createElement("div");
container.classList.add('dialog-dynamic'); container.classList.add("dialog-dynamic");
container.innerHTML = html; container.innerHTML = html;
dialog.appendChild(container); dialog.appendChild(container);
dialog.dataset.loaded = 'true'; dialog.dataset.loaded = "true";
} catch (err) { } catch (err) {
dialog.querySelectorAll('.dialog-dynamic').forEach((el) => el.remove()); dialog.querySelectorAll(".dialog-dynamic").forEach((el) => el.remove());
const errorNode = document.createElement('div'); const errorNode = document.createElement("div");
errorNode.classList.add('dialog-dynamic'); errorNode.classList.add("dialog-dynamic");
errorNode.textContent = 'Failed to load content.'; errorNode.textContent = "Failed to load content.";
dialog.appendChild(errorNode); dialog.appendChild(errorNode);
console.warn('Dialog content load error:', err); console.warn("Dialog content load error:", err);
} }
} }
dialog.showModal(); dialog.showModal();
dialog.classList.remove('closing'); dialog.classList.remove("closing");
}); });
if (closeButton) { if (closeButton) {
closeButton.addEventListener('click', () => { closeButton.addEventListener("click", () => {
dialog.classList.add('closing'); dialog.classList.add("closing");
setTimeout(() => dialog.close(), 200); setTimeout(() => dialog.close(), 200);
}); });
} }
dialog.addEventListener('click', (event) => { dialog.addEventListener("click", (event) => {
const rect = dialog.getBoundingClientRect(); const rect = dialog.getBoundingClientRect();
const outsideClick = const outsideClick =
event.clientX < rect.left || event.clientX < rect.left ||
@ -69,14 +69,14 @@ window.addEventListener('load', () => {
event.clientY > rect.bottom; event.clientY > rect.bottom;
if (outsideClick) { if (outsideClick) {
dialog.classList.add('closing'); dialog.classList.add("closing");
setTimeout(() => dialog.close(), 200); setTimeout(() => dialog.close(), 200);
} }
}); });
dialog.addEventListener('cancel', (event) => { dialog.addEventListener("cancel", (event) => {
event.preventDefault(); event.preventDefault();
dialog.classList.add('closing'); dialog.classList.add("closing");
setTimeout(() => dialog.close(), 200); setTimeout(() => dialog.close(), 200);
}); });
}); });
@ -84,23 +84,23 @@ window.addEventListener('load', () => {
// text toggle for media pages // text toggle for media pages
(() => { (() => {
const button = document.querySelector('[data-toggle-button]'); const button = document.querySelector("[data-toggle-button]");
const content = document.querySelector('[data-toggle-content]'); const content = document.querySelector("[data-toggle-content]");
const text = document.querySelectorAll('[data-toggle-content] p'); const text = document.querySelectorAll("[data-toggle-content] p");
const minHeight = 500; // this needs to match the height set on [data-toggle-content].text-toggle-hidden in text-toggle.css const minHeight = 500; // this needs to match the height set on [data-toggle-content].text-toggle-hidden in text-toggle.css
const interiorHeight = Array.from(text).reduce((acc, node) => acc + node.scrollHeight, 0); const interiorHeight = Array.from(text).reduce((acc, node) => acc + node.scrollHeight, 0);
if (!button || !content || !text.length) return; if (!button || !content || !text.length) return;
if (interiorHeight < minHeight) { if (interiorHeight < minHeight) {
content.classList.remove('text-toggle-hidden'); content.classList.remove("text-toggle-hidden");
button.style.display = 'none'; button.style.display = "none";
return; return;
} }
button.addEventListener('click', () => { button.addEventListener("click", () => {
const isHidden = content.classList.toggle('text-toggle-hidden'); const isHidden = content.classList.toggle("text-toggle-hidden");
button.textContent = isHidden ? 'Show more' : 'Show less'; button.textContent = isHidden ? "Show more" : "Show less";
}); });
})(); })();
}); });

View file

@ -1,16 +1,16 @@
const cacheName = 'coryd.dev-static-assets'; const cacheName = "coryd.dev-static-assets";
const staticAssets = [ const staticAssets = [
'/assets/styles/index.css', "/assets/styles/index.css",
'/assets/styles/noscript.css', "/assets/styles/noscript.css",
'/assets/fonts/sg.woff2', "/assets/fonts/sg.woff2",
'/assets/fonts/dm.woff2', "/assets/fonts/dm.woff2",
'/assets/fonts/dmi.woff2', "/assets/fonts/dmi.woff2",
'/assets/fonts/ml.woff2', "/assets/fonts/ml.woff2",
'/assets/scripts/index.js', "/assets/scripts/index.js",
'/assets/scripts/components/select-pagination.js' "/assets/scripts/components/select-pagination.js"
]; ];
self.addEventListener('install', (event) => { self.addEventListener("install", (event) => {
event.waitUntil( event.waitUntil(
caches caches
.open(cacheName) .open(cacheName)
@ -19,7 +19,7 @@ self.addEventListener('install', (event) => {
); );
}); });
self.addEventListener('activate', (event) => { self.addEventListener("activate", (event) => {
event.waitUntil( event.waitUntil(
(async () => { (async () => {
const keys = await caches.keys(); const keys = await caches.keys();
@ -32,11 +32,11 @@ self.addEventListener('activate', (event) => {
); );
}); });
self.addEventListener('fetch', (event) => { self.addEventListener("fetch", (event) => {
const request = event.request; const request = event.request;
if (request.method !== 'GET') return; if (request.method !== "GET") return;
if (request.headers.get('Accept').includes('text/html')) { if (request.headers.get("Accept").includes("text/html")) {
event.respondWith( event.respondWith(
(async () => { (async () => {
try { try {

View file

@ -1,46 +1,46 @@
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: "Space Grotesk";
src: url('/assets/fonts/sg.woff2') format('woff2'); src: url("/assets/fonts/sg.woff2") format("woff2");
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'DM Sans'; font-family: "DM Sans";
src: url('/assets/fonts/dm-regular.woff2') format('woff2'); src: url("/assets/fonts/dm-regular.woff2") format("woff2");
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'DM Sans'; font-family: "DM Sans";
src: url('/assets/fonts/dm-bold.woff2') format('woff2'); src: url("/assets/fonts/dm-bold.woff2") format("woff2");
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'DM Sans'; font-family: "DM Sans";
src: url('/assets/fonts/dm-regular-italic.woff2') format('woff2'); src: url("/assets/fonts/dm-regular-italic.woff2") format("woff2");
font-weight: 400; font-weight: 400;
font-style: italic; font-style: italic;
font-display: optional; font-display: optional;
} }
@font-face { @font-face {
font-family: 'DM Sans'; font-family: "DM Sans";
src: url('/assets/fonts/dm-bold-italic.woff2') format('woff2'); src: url("/assets/fonts/dm-bold-italic.woff2") format("woff2");
font-weight: 700; font-weight: 700;
font-style: italic; font-style: italic;
font-display: optional; font-display: optional;
} }
@font-face { @font-face {
font-family: 'MonoLisa'; font-family: "MonoLisa";
src: url('/assets/fonts/ml.woff2') format('woff2'); src: url("/assets/fonts/ml.woff2") format("woff2");
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;

View file

@ -443,7 +443,7 @@ td {
text-overflow: ellipsis; text-overflow: ellipsis;
&::after { &::after {
content: ''; content: "";
position: absolute; position: absolute;
inset-block-start: 0; inset-block-start: 0;
inset-inline-end: 0; inset-inline-end: 0;

View file

@ -6,7 +6,7 @@
box-sizing: border-box; box-sizing: border-box;
} }
:where([hidden]:not([hidden='until-found'])) { :where([hidden]:not([hidden="until-found"])) {
display: none !important; display: none !important;
} }
@ -49,7 +49,7 @@
resize: block; resize: block;
} }
:where(button, label, select, summary, [role='button'], [role='option']) { :where(button, label, select, summary, [role="button"], [role="option"]) {
cursor: pointer; cursor: pointer;
} }

View file

@ -71,10 +71,10 @@
--border-gray: 1px solid var(--gray-light); --border-gray: 1px solid var(--gray-light);
/* fonts */ /* fonts */
--font-body: 'DM Sans', Helvetica Neue, Helvetica, Arial, system-ui, sans-serif; --font-body: "DM Sans", Helvetica Neue, Helvetica, Arial, system-ui, sans-serif;
--font-heading: 'Space Grotesk', 'Arial Black', 'Arial Bold', Gadget, sans-serif; --font-heading: "Space Grotesk", "Arial Black", "Arial Bold", Gadget, sans-serif;
--font-code: --font-code:
'MonoLisa', SFMono-Regular, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; "MonoLisa", SFMono-Regular, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
/* text */ /* text */
--font-size-xs: 0.7rem; --font-size-xs: 0.7rem;

View file

@ -1,4 +1,4 @@
@import url('./text-toggle.css'); @import url("./text-toggle.css");
button:not([data-dialog-button]), button:not([data-dialog-button]),
.button { .button {

View file

@ -7,12 +7,12 @@ input {
accent-color: var(--section-color, var(--accent-color)); accent-color: var(--section-color, var(--accent-color));
} }
input:not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']), input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
textarea { textarea {
width: var(--sizing-full); width: var(--sizing-full);
} }
input:not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']), input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
textarea, textarea,
select { select {
color: var(--text-color); color: var(--text-color);
@ -23,7 +23,7 @@ select {
} }
form, form,
input:not([type='button']):not([type='submit']):not([type='reset']):not([type='checkbox']), input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
textarea { textarea {
margin-bottom: var(--spacing-base); margin-bottom: var(--spacing-base);
} }
@ -50,7 +50,7 @@ label svg {
cursor: pointer; cursor: pointer;
} }
detail label:has(input[type='checkbox']) { detail label:has(input[type="checkbox"]) {
display: inline-flex; display: inline-flex;
gap: var(--spacing-xs); gap: var(--spacing-xs);
} }

View file

@ -11,7 +11,7 @@
&::after { &::after {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
content: ''; content: "";
box-shadow: var(--box-shadow-text-toggle); box-shadow: var(--box-shadow-text-toggle);
width: var(--sizing-full); width: var(--sizing-full);
height: calc(var(--sizing-full) * 0.2); height: calc(var(--sizing-full) * 0.2);

View file

@ -1,36 +1,36 @@
@layer reset, defaults, base, page, components, plugins; @layer reset, defaults, base, page, components, plugins;
/* style resets */ /* style resets */
@import url('./base/reset.css') layer(reset); @import url("./base/reset.css") layer(reset);
/* core defaults */ /* core defaults */
@import url('./base/fonts.css') layer(defaults); @import url("./base/fonts.css") layer(defaults);
@import url('./base/vars.css') layer(defaults); @import url("./base/vars.css") layer(defaults);
/* base styles */ /* base styles */
@import url('./base/index.css') layer(base); @import url("./base/index.css") layer(base);
/* page styles */ /* page styles */
@import url('./pages/contact.css') layer(page); @import url("./pages/contact.css") layer(page);
@import url('./pages/links.css') layer(page); @import url("./pages/links.css") layer(page);
@import url('./pages/media.css') layer(page); @import url("./pages/media.css") layer(page);
@import url('./pages/music.css') layer(page); @import url("./pages/music.css") layer(page);
@import url('./pages/reading.css') layer(page); @import url("./pages/reading.css") layer(page);
@import url('./pages/watching.css') layer(page); @import url("./pages/watching.css") layer(page);
@import url('./pages/webrings.css') layer(page); @import url("./pages/webrings.css") layer(page);
/* plugins */ /* plugins */
@import url('./plugins/prism.css') layer(plugins); @import url("./plugins/prism.css") layer(plugins);
/* component styles */ /* component styles */
@import url('./components/banners.css') layer(components); @import url("./components/banners.css") layer(components);
@import url('./components/buttons.css') layer(components); @import url("./components/buttons.css") layer(components);
@import url('./components/forms.css') layer(components); @import url("./components/forms.css") layer(components);
@import url('./components/header.css') layer(components); @import url("./components/header.css") layer(components);
@import url('./components/media-grid.css') layer(components); @import url("./components/media-grid.css") layer(components);
@import url('./components/nav.css') layer(components); @import url("./components/nav.css") layer(components);
@import url('./components/dialog.css') layer(components); @import url("./components/dialog.css") layer(components);
@import url('./components/music-chart.css') layer(components); @import url("./components/music-chart.css") layer(components);
@import url('./components/paginator.css') layer(components); @import url("./components/paginator.css") layer(components);
@import url('./components/progress-bar.css') layer(components); @import url("./components/progress-bar.css") layer(components);
@import url('./components/youtube-player.css') layer(components); @import url("./components/youtube-player.css") layer(components);

View file

@ -1,23 +1,23 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAlbumReleases = async () => { const fetchAlbumReleases = async () => {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_album_releases`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_album_releases`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
const pacificNow = new Date().toLocaleString('en-US', { const pacificNow = new Date().toLocaleString("en-US", {
timeZone: 'America/Los_Angeles' timeZone: "America/Los_Angeles"
}); });
const pacificDate = new Date(pacificNow); const pacificDate = new Date(pacificNow);
pacificDate.setHours(0, 0, 0, 0); pacificDate.setHours(0, 0, 0, 0);
@ -29,11 +29,11 @@ const fetchAlbumReleases = async () => {
return { return {
...album, ...album,
description: album.artist?.description || 'No description', description: album.artist?.description || "No description",
date: releaseDate.toLocaleDateString('en-US', { date: releaseDate.toLocaleDateString("en-US", {
year: 'numeric', year: "numeric",
month: 'long', month: "long",
day: 'numeric' day: "numeric"
}) })
}; };
}) })
@ -45,7 +45,7 @@ const fetchAlbumReleases = async () => {
return { all, upcoming }; return { all, upcoming };
} catch (error) { } catch (error) {
console.error('Error fetching and processing album releases:', error); console.error("Error fetching and processing album releases:", error);
return { all: [], upcoming: [] }; return { all: [], upcoming: [] };
} }
}; };

View file

@ -1,16 +1,16 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
export default async function fetchAllActivity() { export default async function fetchAllActivity() {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_all_activity`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_all_activity`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -18,7 +18,7 @@ export default async function fetchAllActivity() {
return data?.[0] || []; return data?.[0] || [];
} catch (error) { } catch (error) {
console.error('Error fetching activity:', error); console.error("Error fetching activity:", error);
return []; return [];
} }
} }

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchBlogroll = async () => { const fetchBlogroll = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_blogroll`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_blogroll`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching and processing the blogroll:', error); console.error("Error fetching and processing the blogroll:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllBooks = async () => { const fetchAllBooks = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_books?order=date_finished.desc`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_books?order=date_finished.desc`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching books:', error); console.error("Error fetching books:", error);
return []; return [];
} }
}; };
@ -42,7 +42,7 @@ export default async function () {
const booksForCurrentYear = const booksForCurrentYear =
sortedByYear sortedByYear
.find((yearGroup) => yearGroup.value === currentYear) .find((yearGroup) => yearGroup.value === currentYear)
?.data.filter((book) => book.status === 'finished') || []; ?.data.filter((book) => book.status === "finished") || [];
return { return {
all: books, all: books,

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllConcerts = async () => { const fetchAllConcerts = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_concerts`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_concerts`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching concerts:', error); console.error("Error fetching concerts:", error);
return []; return [];
} }
}; };
@ -32,7 +32,7 @@ export default async function () {
const concerts = await fetchAllConcerts(); const concerts = await fetchAllConcerts();
return processConcerts(concerts); return processConcerts(concerts);
} catch (error) { } catch (error) {
console.error('Error fetching and processing concerts data:', error); console.error("Error fetching and processing concerts data:", error);
return []; return [];
} }
} }

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchFeeds = async () => { const fetchFeeds = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_feeds?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_feeds?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching feed metadata:', error); console.error("Error fetching feed metadata:", error);
return []; return [];
} }
}; };
@ -24,12 +24,12 @@ const fetchFeeds = async () => {
const fetchFeedData = async (feedKey) => { const fetchFeedData = async (feedKey) => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/rpc/get_feed_data`, { return await EleventyFetch(`${POSTGREST_URL}/rpc/get_feed_data`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
}, },
body: JSON.stringify({ feed_key: feedKey }) body: JSON.stringify({ feed_key: feedKey })

View file

@ -1,16 +1,16 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchGlobals = async () => { const fetchGlobals = async () => {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_globals?select=*`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_globals?select=*`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -18,7 +18,7 @@ const fetchGlobals = async () => {
return data[0]; return data[0];
} catch (error) { } catch (error) {
console.error('Error fetching globals:', error); console.error("Error fetching globals:", error);
return {}; return {};
} }
}; };

View file

@ -1,15 +1,15 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
export default async function () { export default async function () {
const res = await EleventyFetch(`${POSTGREST_URL}/optimized_all_tags?order=tag.asc`, { const res = await EleventyFetch(`${POSTGREST_URL}/optimized_all_tags?order=tag.asc`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -18,7 +18,7 @@ export default async function () {
const groupedMap = new Map(); const groupedMap = new Map();
for (const tag of tags) { for (const tag of tags) {
const letter = /^[a-zA-Z]/.test(tag.tag) ? tag.tag[0].toUpperCase() : '#'; const letter = /^[a-zA-Z]/.test(tag.tag) ? tag.tag[0].toUpperCase() : "#";
if (!groupedMap.has(letter)) groupedMap.set(letter, []); if (!groupedMap.has(letter)) groupedMap.set(letter, []);

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchHeaders = async () => { const fetchHeaders = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_headers?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_headers?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching header data:', error); console.error("Error fetching header data:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllLinks = async () => { const fetchAllLinks = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_links?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_links?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching links:', error); console.error("Error fetching links:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllMovies = async () => { const fetchAllMovies = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_movies?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_movies?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching movies:', error); console.error("Error fetching movies:", error);
return []; return [];
} }
}; };
@ -48,7 +48,7 @@ export default async function () {
feed: movies.filter((movie) => movie.feed) feed: movies.filter((movie) => movie.feed)
}; };
} catch (error) { } catch (error) {
console.error('Error fetching and processing movies data:', error); console.error("Error fetching and processing movies data:", error);
return { return {
movies: [], movies: [],
watchHistory: [], watchHistory: [],

View file

@ -1,16 +1,16 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchDataFromView = async (viewName) => { const fetchDataFromView = async (viewName) => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/${viewName}?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/${viewName}?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -34,15 +34,15 @@ export default async function fetchMusicData() {
monthAlbums, monthAlbums,
monthGenres monthGenres
] = await Promise.all([ ] = await Promise.all([
fetchDataFromView('recent_tracks'), fetchDataFromView("recent_tracks"),
fetchDataFromView('week_tracks'), fetchDataFromView("week_tracks"),
fetchDataFromView('week_artists'), fetchDataFromView("week_artists"),
fetchDataFromView('week_albums'), fetchDataFromView("week_albums"),
fetchDataFromView('week_genres'), fetchDataFromView("week_genres"),
fetchDataFromView('month_tracks'), fetchDataFromView("month_tracks"),
fetchDataFromView('month_artists'), fetchDataFromView("month_artists"),
fetchDataFromView('month_albums'), fetchDataFromView("month_albums"),
fetchDataFromView('month_genres') fetchDataFromView("month_genres")
]); ]);
return { return {
@ -52,7 +52,7 @@ export default async function fetchMusicData() {
artists: weekArtists, artists: weekArtists,
albums: weekAlbums, albums: weekAlbums,
genres: weekGenres, genres: weekGenres,
totalTracks: weekTracks.reduce((acc, track) => acc + track.plays, 0).toLocaleString('en-US') totalTracks: weekTracks.reduce((acc, track) => acc + track.plays, 0).toLocaleString("en-US")
}, },
month: { month: {
tracks: monthTracks, tracks: monthTracks,
@ -61,11 +61,11 @@ export default async function fetchMusicData() {
genres: monthGenres, genres: monthGenres,
totalTracks: monthTracks totalTracks: monthTracks
.reduce((acc, track) => acc + track.plays, 0) .reduce((acc, track) => acc + track.plays, 0)
.toLocaleString('en-US') .toLocaleString("en-US")
} }
}; };
} catch (error) { } catch (error) {
console.error('Error fetching and processing music data:', error); console.error("Error fetching and processing music data:", error);
return { return {
recent: [], recent: [],
week: { week: {
@ -73,14 +73,14 @@ export default async function fetchMusicData() {
artists: [], artists: [],
albums: [], albums: [],
genres: [], genres: [],
totalTracks: '0' totalTracks: "0"
}, },
month: { month: {
tracks: [], tracks: [],
artists: [], artists: [],
albums: [], albums: [],
genres: [], genres: [],
totalTracks: '0' totalTracks: "0"
} }
}; };
} }

View file

@ -1,16 +1,16 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllNavigation = async () => { const fetchAllNavigation = async () => {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_navigation?select=*`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_navigation?select=*`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -40,7 +40,7 @@ const fetchAllNavigation = async () => {
return nav; return nav;
} catch (error) { } catch (error) {
console.error('Error fetching navigation data:', error); console.error("Error fetching navigation data:", error);
return {}; return {};
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllPages = async () => { const fetchAllPages = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_pages?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_pages?select=*`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching pages:', error); console.error("Error fetching pages:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllPosts = async () => { const fetchAllPosts = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_posts?select=*&order=date.desc`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_posts?select=*&order=date.desc`, {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching posts:', error); console.error("Error fetching posts:", error);
return []; return [];
} }
}; };

View file

@ -1,16 +1,16 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
export default async function () { export default async function () {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_recent_activity`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_recent_activity`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -20,7 +20,7 @@ export default async function () {
return feeds.filter((item) => item !== null); return feeds.filter((item) => item !== null);
} catch (error) { } catch (error) {
console.error('Error fetching recent activity:', error); console.error("Error fetching recent activity:", error);
return []; return [];
} }
} }

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchRedirects = async () => { const fetchRedirects = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_redirects?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_redirects?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching redirect data:', error); console.error("Error fetching redirect data:", error);
return []; return [];
} }
}; };

View file

@ -1,30 +1,30 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllRobots = async () => { const fetchAllRobots = async () => {
try { try {
const data = await EleventyFetch(`${POSTGREST_URL}/optimized_robots?select=path,user_agents`, { const data = await EleventyFetch(`${POSTGREST_URL}/optimized_robots?select=path,user_agents`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
const sortedData = data.sort((a, b) => { const sortedData = data.sort((a, b) => {
const aHasWildcard = a.user_agents.includes('*') ? 0 : 1; const aHasWildcard = a.user_agents.includes("*") ? 0 : 1;
const bHasWildcard = b.user_agents.includes('*') ? 0 : 1; const bHasWildcard = b.user_agents.includes("*") ? 0 : 1;
return aHasWildcard - bHasWildcard || a.path.localeCompare(b.path); return aHasWildcard - bHasWildcard || a.path.localeCompare(b.path);
}); });
return sortedData; return sortedData;
} catch (error) { } catch (error) {
console.error('Error fetching robot data:', error); console.error("Error fetching robot data:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchSitemap = async () => { const fetchSitemap = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_sitemap?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_sitemap?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching sitemap entries:', error); console.error("Error fetching sitemap entries:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchStats = async () => { const fetchStats = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_stats?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_stats?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching stats data:', error); console.error("Error fetching stats data:", error);
return []; return [];
} }
}; };

View file

@ -1,4 +1,4 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
@ -7,12 +7,12 @@ const fetchTopAlbums = async () => {
const data = await EleventyFetch( const data = await EleventyFetch(
`${POSTGREST_URL}/optimized_albums?select=table&order=total_plays_raw.desc&limit=8`, `${POSTGREST_URL}/optimized_albums?select=table&order=total_plays_raw.desc&limit=8`,
{ {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -21,7 +21,7 @@ const fetchTopAlbums = async () => {
return data; return data;
} catch (error) { } catch (error) {
console.error('Error fetching top albums:', error); console.error("Error fetching top albums:", error);
return {}; return {};
} }
}; };

View file

@ -1,4 +1,4 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
@ -7,12 +7,12 @@ const fetchTopArtists = async () => {
const data = await EleventyFetch( const data = await EleventyFetch(
`${POSTGREST_URL}/optimized_artists?select=table&order=total_plays_raw.desc&limit=8`, `${POSTGREST_URL}/optimized_artists?select=table&order=total_plays_raw.desc&limit=8`,
{ {
duration: '1d', duration: "1d",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
@ -21,7 +21,7 @@ const fetchTopArtists = async () => {
return data; return data;
} catch (error) { } catch (error) {
console.error('Error fetching top artists:', error); console.error("Error fetching top artists:", error);
return {}; return {};
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchTopTags = async () => { const fetchTopTags = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/rpc/get_top_tag_groups`, { return await EleventyFetch(`${POSTGREST_URL}/rpc/get_top_tag_groups`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching top tag entries:', error); console.error("Error fetching top tag entries:", error);
return []; return [];
} }
}; };

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchAllShows = async () => { const fetchAllShows = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_shows?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_shows?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching shows:', error); console.error("Error fetching shows:", error);
return []; return [];
} }
}; };
@ -46,7 +46,7 @@ export default async function () {
.sort((a, b) => a.title.localeCompare(b.title)) .sort((a, b) => a.title.localeCompare(b.title))
}; };
} catch (error) { } catch (error) {
console.error('Error fetching and processing shows data:', error); console.error("Error fetching and processing shows data:", error);
return { return {
shows: [], shows: [],

View file

@ -1,22 +1,22 @@
import EleventyFetch from '@11ty/eleventy-fetch'; import EleventyFetch from "@11ty/eleventy-fetch";
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env; const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
const fetchUpcomingShows = async () => { const fetchUpcomingShows = async () => {
try { try {
return await EleventyFetch(`${POSTGREST_URL}/optimized_scheduled_shows?select=*`, { return await EleventyFetch(`${POSTGREST_URL}/optimized_scheduled_shows?select=*`, {
duration: '1h', duration: "1h",
type: 'json', type: "json",
fetchOptions: { fetchOptions: {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Bearer ${POSTGREST_API_KEY}` Authorization: `Bearer ${POSTGREST_API_KEY}`
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('Error fetching upcoming shows:', error); console.error("Error fetching upcoming shows:", error);
return []; return [];
} }
}; };

View file

@ -1,29 +1,41 @@
{% assign media = artists | concat:books | concat:genres | concat:movies | concat:posts | concat:shows %} {% assign media = artists
| concat: books
| concat: genres
| concat: movies
| concat: posts
| concat: shows
%}
{% if media.size > 0 %} {% if media.size > 0 %}
<div class="associated-media"> <div class="associated-media">
{% assign sections = {% assign sections = 'artists:headphones:music:Related artist(s):true,'
"artists:headphones:music:Related artist(s):true," | append: | append: 'books:books:books:Related book(s):true,'
"books:books:books:Related book(s):true," | append: | append: 'genres:headphones:music:Related genre(s):false,'
"genres:headphones:music:Related genre(s):false," | append: | append: 'movies:movie:movies:Related movie(s):true,'
"movies:movie:movies:Related movie(s):true," | append: | append: 'posts:article:article:Related post(s):false,'
"posts:article:article:Related post(s):false," | append: | append: 'shows:device-tv-old:tv:Related show(s):true'
"shows:device-tv-old:tv:Related show(s):true" | split: ','
| split:"," %} %}
{% for section in sections %} {% for section in sections %}
{% assign parts = section | split:":" %} {% assign parts = section | split: ':' %}
{% assign key = parts[0] %} {% assign key = parts[0] %}
{% assign icon = parts[1] %} {% assign icon = parts[1] %}
{% assign css_class = parts[2] %} {% assign css_class = parts[2] %}
{% assign label = parts[3] %} {% assign label = parts[3] %}
{% assign hasGrid = parts[4] == "true" %} {% assign hasGrid = parts[4] == "true" %}
{% assign items = nil %} {% assign items = null %}
{% case key %} {% case key %}
{% when "artists" %} {% assign items = artists %} {% when 'artists' %}
{% when "books" %} {% assign items = books %} {% assign items = artists %}
{% when "genres" %} {% assign items = genres %} {% when 'books' %}
{% when "movies" %} {% assign items = movies %} {% assign items = books %}
{% when "posts" %} {% assign items = posts %} {% when 'genres' %}
{% when "shows" %} {% assign items = shows %} {% assign items = genres %}
{% when 'movies' %}
{% assign items = movies %}
{% when 'posts' %}
{% assign items = posts %}
{% when 'shows' %}
{% assign items = shows %}
{% endcase %} {% endcase %}
{% if items and items.size > 0 %} {% if items and items.size > 0 %}
<h3 id="{{ key }}" class="{{ css_class }}"> <h3 id="{{ key }}" class="{{ css_class }}">
@ -31,27 +43,25 @@
{{ label }} {{ label }}
</h3> </h3>
{% if hasGrid %} {% if hasGrid %}
{% assign shape = "square" %} {% assign shape = 'square' %}
{% if key == "books" or key == "movies" or key == "shows" %} {% if key == 'books' or key == 'movies' or key == 'shows' %}
{% assign shape = "vertical" %} {% assign shape = 'vertical' %}
{% endif %} {% endif %}
{% render "static/media/grid.liquid", {% render 'static/media/grid.liquid', data: items, shape: shape %}
data:items,
shape:shape
%}
{% else %} {% else %}
<ul> <ul>
{% for item in items %} {% for item in items %}
<li class="{{ css_class }}"> <li class="{{ css_class }}">
<a href="{{ item.url }}">{{ item.title | default: item.name }}</a> <a href="{{ item.url }}">{{ item.title | default: item.name }}</a>
{% if key == "artists" and item.total_plays > 0 %} {% if key == 'artists' and item.total_plays > 0 %}
({{ item.total_plays }} {{ item.total_plays | pluralize:"play" }}) ({{ item.total_plays }}
{% elsif key == "books" %} {{ item.total_plays | pluralize: 'play' }})
{% elsif key == 'books' %}
by {{ item.author }} by {{ item.author }}
{% elsif key == "movies" or key == "shows" %} {% elsif key == 'movies' or key == 'shows' %}
({{ item.year }}) ({{ item.year }})
{% elsif key == "posts" %} {% elsif key == 'posts' %}
({{ item.date | date:"%B %e, %Y" }}) ({{ item.date | date: '%B %e, %Y' }})
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}

View file

@ -1,3 +1,6 @@
<div class="banner calendar"> <div class="banner calendar">
<p>{% tablericon "calendar" %} <a href="{{ url }}">{{ text }}</a>.</p> <p>
{% tablericon "calendar" %}
<a href="{{ url }}">{{ text }}</a>.
</p>
</div> </div>

View file

@ -1,3 +1,7 @@
<div class="banner forgejo"> <div class="banner forgejo">
<p>{% tablericon "git-merge" %} Take a look at <a href="{{ url }}">the repository for this project</a> on my <a href="https://forgejo.org">Forgejo</a> instance.</p> <p>
{% tablericon "git-merge" %} Take a look at
<a href="{{ url }}">the repository for this project</a> on my
<a href="https://forgejo.org">Forgejo</a> instance.
</p>
</div> </div>

View file

@ -1,3 +1,7 @@
<div class="banner github"> <div class="banner github">
<p>{% tablericon "brand-github" %} Take a look at <a href="{{ url }}">the GitHub repository for this project</a>. (Give it a star if you feel like it.)</p> <p>
{% tablericon "brand-github" %} Take a look at
<a href="{{ url }}">the GitHub repository for this project</a>. (Give it a star if you feel like
it.)
</p>
</div> </div>

View file

@ -1,3 +1,7 @@
<div class="banner npm"> <div class="banner npm">
<p>{% tablericon "brand-npm" %} <a href="{{ url }}">You can take a look at this package on NPM</a> or install it by running <code>{{ command }}</code>.</p> <p>
{% tablericon "brand-npm" %}
<a href="{{ url }}">You can take a look at this package on NPM</a> or install it by running
<code>{{ command }}</code>.
</p>
</div> </div>

View file

@ -1,5 +1,8 @@
{%- if isOldPost -%} {%- if isOldPost -%}
<div class="banner old-post"> <div class="banner old-post">
<p>{% tablericon "clock-x" %} This post is over 3 years old. I've probably changed my mind since it was written and it <em>could</em> be out of date.</p> <p>
{% tablericon "clock-x" %} This post is over 3 years old. I've probably changed my mind since
it was written and it <em>could</em> be out of date.
</p>
</div> </div>
{%- endif -%} {%- endif -%}

View file

@ -1,3 +1,6 @@
<div class="banner rss"> <div class="banner rss">
<p>{% tablericon "rss" %} <a href="{{ url }}">{{ text }}</a>.</p> <p>
{% tablericon "rss" %}
<a href="{{ url }}">{{ text }}</a>.
</p>
</div> </div>

View file

@ -1,3 +1,6 @@
<div class="banner warning"> <div class="banner warning">
<p>{% tablericon "alert-triangle" %} {{ text }}</p> <p>
{% tablericon "alert-triangle" %}
{{ text }}
</p>
</div> </div>

View file

@ -5,15 +5,25 @@
{{ label }} {{ label }}
{%- endif -%} {%- endif -%}
{%- endcapture -%} {%- endcapture -%}
{% assign dialogId = id | default:"dialog-controls" %} {% assign dialogId = id | default: 'dialog-controls' %}
<button class="dialog-open client-side" aria-label="{{ label }}" data-dialog-trigger="{{ dialogId }}" data-dialog-button> <button
class="dialog-open client-side"
aria-label="{{ label }}"
data-dialog-trigger="{{ dialogId }}"
data-dialog-button
>
{{ labelContent }} {{ labelContent }}
</button> </button>
<dialog <dialog
id="dialog-{{ dialogId }}" id="dialog-{{ dialogId }}"
class="client-side" class="client-side"
{% if dynamic %}data-dynamic="{{ dynamic }}"{% endif %} {% if dynamic %}
{% if markdown %}data-markdown="{{ markdown }}"{% endif %}> data-dynamic="{{ dynamic }}"
{% endif %}
{% if markdown %}
data-markdown="{{ markdown }}"
{% endif %}
>
<button class="dialog-close" aria-label="Close dialog" data-dialog-button> <button class="dialog-close" aria-label="Close dialog" data-dialog-button>
{% tablericon "circle-x" %} {% tablericon "circle-x" %}
</button> </button>

View file

@ -4,9 +4,11 @@
{{ image }}?class=bannermd&type=webp 512w, {{ image }}?class=bannermd&type=webp 512w,
{{ image }}?class=bannerbase&type=webp 1024w {{ image }}?class=bannerbase&type=webp 1024w
" "
sizes="(max-width: 450px) 256px, sizes="
(max-width: 450px) 256px,
(max-width: 850px) 512px, (max-width: 850px) 512px,
1024px" 1024px
"
src="{{ image }}?class=bannersm&type=webp" src="{{ image }}?class=bannersm&type=webp"
alt="{{ alt | replaceQuotes }}" alt="{{ alt | replaceQuotes }}"
class="image-banner" class="image-banner"

View file

@ -1,41 +1,22 @@
{%- for block in blocks -%} {%- for block in blocks -%}
{%- case block.type -%} {%- case block.type -%}
{%- when "calendar_banner" -%} {%- when 'calendar_banner' -%}
{% render "static/blocks/banners/calendar.liquid", {% render 'static/blocks/banners/calendar.liquid', url: block.url, text: block.text %}
url:block.url, {%- when 'divider' -%}
text:block.text
%}
{%- when "divider" -%}
{{ block.markup | markdown }} {{ block.markup | markdown }}
{%- when "forgejo_banner" -%} {%- when 'forgejo_banner' -%}
{% render "static/blocks/banners/forgejo.liquid", {% render 'static/blocks/banners/forgejo.liquid', url: block.url %}
url:block.url {%- when 'github_banner' -%}
%} {% render 'static/blocks/banners/github.liquid', url: block.url %}
{%- when "github_banner" -%} {%- when 'hero' -%}
{% render "static/blocks/banners/github.liquid", {% render 'static/blocks/hero.liquid', globals: globals, image: block.image, alt: block.alt %}
url:block.url {%- when 'markdown' -%}
%}
{%- when "hero" -%}
{% render "static/blocks/hero.liquid",
globals:globals,
image:block.image,
alt:block.alt
%}
{%- when "markdown" -%}
{{ block.text | markdown }} {{ block.text | markdown }}
{%- when "npm_banner" -%} {%- when 'npm_banner' -%}
{% render "static/blocks/banners/npm.liquid", {% render 'static/blocks/banners/npm.liquid', url: block.url, command: block.command %}
url:block.url, {%- when 'rss_banner' -%}
command:block.command {% render 'static/blocks/banners/rss.liquid', url: block.url, text: block.text %}
%} {%- when 'youtube_player' -%}
{%- when "rss_banner" -%} {% render 'static/blocks/youtube-player.liquid', url: block.url %}
{% render "static/blocks/banners/rss.liquid",
url:block.url,
text:block.text
%}
{%- when "youtube_player" -%}
{% render "static/blocks/youtube-player.liquid",
url:block.url
%}
{%- endcase -%} {%- endcase -%}
{%- endfor -%} {%- endfor -%}

View file

@ -1 +1,8 @@
{% if tags %}<p class="tags tags-small">{%- for tag in tags %} {% assign tagLowercase = tag | downcase -%}<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>{%- endfor -%}</p>{% endif %} {% if tags %}
<p class="tags tags-small">
{%- for tag in tags %}
{% assign tagLowercase = tag | downcase -%}
<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>
{%- endfor -%}
</p>
{% endif %}

View file

@ -1 +1,7 @@
<p><mark>{{ label }}</mark>{%- for tag in tags %} {% assign tagLowercase = tag.tag | downcase -%}<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>{%- endfor -%}</p> <p>
<mark>{{ label }}</mark>
{%- for tag in tags %}
{% assign tagLowercase = tag.tag | downcase -%}
<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>
{%- endfor -%}
</p>

View file

@ -1,2 +1,6 @@
<script type="module" src="/assets/scripts/components/youtube-video-element.js?v={% appVersion %}" defer></script> <script
type="module"
src="/assets/scripts/components/youtube-video-element.js?v={% appVersion %}"
defer
></script>
<youtube-video controls src="{{ url }}"></youtube-video> <youtube-video controls src="{{ url }}"></youtube-video>

View file

@ -1,7 +1,5 @@
<article class="intro"> <article class="intro">
{{ intro }} {{ intro }}
{% render "dynamic/media/now-playing.php.liquid", {% render 'dynamic/media/now-playing.php.liquid', section: 'music' %}
section:"music" <hr>
%}
<hr />
</article> </article>

View file

@ -5,22 +5,22 @@
{% tablericon "star" %} {% tablericon "star" %}
{%- endif -%} {%- endif -%}
<time datetime="{{ item.content_date }}"> <time datetime="{{ item.content_date }}">
{{ item.content_date | date:"%B %e, %Y" }} {{ item.content_date | date: '%B %e, %Y' }}
</time> </time>
• {{ item.label }} • {{ item.label }}
{%- if item.notes -%} {%- if item.notes -%}
{% assign notes = item.notes | markdown %} {% assign notes = item.notes | markdown %}
{% render "static/blocks/dialog.liquid", {% render 'static/blocks/dialog.liquid',
icon:"info-circle", icon: 'info-circle',
label:"View info about this concert" label: 'View info about this concert',
dynamic:"optimized_concerts", dynamic: 'optimized_concerts',
markdown:"concert_notes", markdown: 'concert_notes',
id: item.id id: item.id
%} %}
{%- endif -%} {%- endif -%}
</aside> </aside>
<h3> <h3>
{%- if item.type == "concerts" -%} {%- if item.type == 'concerts' -%}
{%- capture artistName -%} {%- capture artistName -%}
{%- if item.artist_url -%} {%- if item.artist_url -%}
<a href="{{ item.artist_url }}">{{ item.title | split: ' at ' | first }}</a> <a href="{{ item.artist_url }}">{{ item.title | split: ' at ' | first }}</a>
@ -39,7 +39,7 @@
{% if venue %} at {{ venue }}{% endif %} {% if venue %} at {{ venue }}{% endif %}
{%- else -%} {%- else -%}
<a href="{{ item.url | prepend: globals.url }}">{{ item.title }}</a> <a href="{{ item.url | prepend: globals.url }}">{{ item.title }}</a>
{%- if item.type == "link" and item.author -%} {%- if item.type == 'link' and item.author -%}
<span> via </span> <span> via </span>
{%- if item.author.url -%} {%- if item.author.url -%}
<a href="{{ item.author.url }}">{{ item.author.name }}</a> <a href="{{ item.author.url }}">{{ item.author.name }}</a>
@ -49,8 +49,6 @@
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
</h3> </h3>
{% render "static/blocks/tags.liquid", {% render 'static/blocks/tags.liquid', tags: item.tags %}
tags:item.tags
%}
</article> </article>
{%- endfor -%} {%- endfor -%}

View file

@ -1,13 +1,9 @@
<footer> <footer>
{% render "static/nav/menu.liquid", {% render 'static/nav/menu.liquid', page: page, nav: nav.footer_icons, class: 'social' %}
{% render 'static/nav/menu.liquid',
page: page, page: page,
nav:nav.footer_icons nav: nav.footer_text,
class:"social" class: 'sub-pages',
%}
{% render "static/nav/menu.liquid",
page:page,
nav:nav.footer_text
class:"sub-pages"
separator: true separator: true
%} %}
</footer> </footer>

View file

@ -15,20 +15,12 @@
{%- endcapture -%} {%- endcapture -%}
<section class="main-title"> <section class="main-title">
<h1> <h1>
{%- if normalizedUrl == "/" -%} {%- if normalizedUrl == '/' -%}
{{ headerContent }} {{ headerContent }}
{%- else -%} {%- else -%}
<a href="/" tabindex="0">{{ headerContent }}</a> <a href="/" tabindex="0">{{ headerContent }}</a>
{%- endif -%} {%- endif -%}
</h1> </h1>
{% render "static/nav/menu.liquid", {% render 'static/nav/menu.liquid', page: page, nav: nav.primary_icons, class: 'icons' %}
page:page,
nav:nav.primary_icons
class:"icons"
%}
</section> </section>
{% render "static/nav/menu.liquid", {% render 'static/nav/menu.liquid', page: page, nav: nav.primary, class: 'primary' %}
page:page,
nav:nav.primary
class:"primary"
%}

View file

@ -1,5 +1,5 @@
<div class="media-grid {{ shape | default: square }}"> <div class="media-grid {{ shape | default: square }}">
{%- assign loadingStrategy = loading | default:"lazy" -%} {%- assign loadingStrategy = loading | default: 'lazy' -%}
{%- for item in data limit: count -%} {%- for item in data limit: count -%}
{%- assign alt = item.grid.alt | replaceQuotes -%} {%- assign alt = item.grid.alt | replaceQuotes -%}
{%- assign imageUrl = item.grid.image -%} {%- assign imageUrl = item.grid.image -%}
@ -15,12 +15,12 @@
{% endif %} {% endif %}
</mark> </mark>
{%- endif -%} {%- endif -%}
{%- assign imageClass = "square" -%} {%- assign imageClass = 'square' -%}
{%- assign width = 150 -%} {%- assign width = 150 -%}
{%- assign height = 150 -%} {%- assign height = 150 -%}
{%- case shape -%} {%- case shape -%}
{%- when "vertical" -%} {%- when 'vertical' -%}
{%- assign imageClass = "vertical" -%} {%- assign imageClass = 'vertical' -%}
{%- assign width = 120 -%} {%- assign width = 120 -%}
{%- assign height = 184 -%} {%- assign height = 184 -%}
{%- endcase -%} {%- endcase -%}
@ -41,6 +41,4 @@
</a> </a>
{%- endfor -%} {%- endfor -%}
</div> </div>
{% render "static/nav/paginator.liquid", {% render 'static/nav/paginator.liquid', pagination: pagination %}
pagination:pagination
%}

View file

@ -1,13 +1,14 @@
<div class="chart-item"> <div class="chart-item">
<div class="chart-item-info"> <div class="chart-item-info">
<a class="title" href="{{ item.chart.url }}">{{ item.chart.title }}</a> <a class="title" href="{{ item.chart.url }}">{{ item.chart.title }}</a>
{%- assign playsLabel = item.chart.plays | pluralize:"play" -%} {%- assign playsLabel = item.chart.plays | pluralize: 'play' -%}
<span class="subheader">{{ item.chart.artist }}</span> <span class="subheader">{{ item.chart.artist }}</span>
<span class="subheader">{{ item.chart.plays }} {{ playsLabel }}</span> <span class="subheader">
{{- item.chart.plays }}
{{ playsLabel -}}
</span>
</div> </div>
<div class="chart-item-progress"> <div class="chart-item-progress">
{% render "static/media/progress-bar.liquid", {% render 'static/media/progress-bar.liquid', percentage: item.chart.percentage %}
percentage:item.chart.percentage
%}
</div> </div>
</div> </div>

View file

@ -3,22 +3,16 @@
{%- if count -%} {%- if count -%}
{%- for item in data limit: count -%} {%- for item in data limit: count -%}
<li value="{{ item.chart.rank }}"> <li value="{{ item.chart.rank }}">
{% render "static/media/music/charts/item.liquid", {% render 'static/media/music/charts/item.liquid', item: item %}
item:item
%}
</li> </li>
{%- endfor -%} {%- endfor -%}
{%- else -%} {%- else -%}
{%- for item in pagination.items -%} {%- for item in pagination.items -%}
<li value="{{ item.chart.rank }}"> <li value="{{ item.chart.rank }}">
{% render "static/media/music/charts/item.liquid", {% render 'static/media/music/charts/item.liquid', item: item %}
item:item
%}
</li> </li>
{%- endfor -%} {%- endfor -%}
{%- endif -%} {%- endif -%}
</ol> </ol>
</div> </div>
{% render "static/nav/paginator.liquid", {% render 'static/nav/paginator.liquid', pagination: pagination %}
pagination:pagination
%}

View file

@ -13,7 +13,9 @@
{{ album.table.title }} {{ album.table.title }}
</div> </div>
</td> </td>
<td><a href="{{ album.table.url }}">{{ album.table.artist }}</a></td> <td>
<a href="{{ album.table.url }}">{{ album.table.artist }}</a>
</td>
<td>{{ album.table.plays }}</td> <td>{{ album.table.plays }}</td>
<td>{{ album.table.year }}</td> <td>{{ album.table.year }}</td>
</tr> </tr>

View file

@ -12,7 +12,10 @@
<a href="{{ artist.table.url }}">{{ artist.table.title }}</a> <a href="{{ artist.table.url }}">{{ artist.table.title }}</a>
</div> </div>
</td> </td>
<td>{{ artist.table.emoji }} <a href="{{ artist.table.genre_url }}">{{ artist.table.genre }}</a></td> <td>
{{ artist.table.emoji }}
<a href="{{ artist.table.genre_url }}">{{ artist.table.genre }}</a>
</td>
<td>{{ artist.table.plays }}</td> <td>{{ artist.table.plays }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -9,7 +9,7 @@
({{ movie.year }}) ({{ movie.year }})
</div> </div>
</mark> </mark>
{% render "static/blocks/hero.liquid", {% render 'static/blocks/hero.liquid',
globals: globals, globals: globals,
image: movie.backdrop, image: movie.backdrop,
alt: movie.title alt: movie.title

View file

@ -1,15 +1,41 @@
<meta name="amazonbot" content="noarchive"> <meta name="amazonbot" content="noarchive">
<meta name="bingbot" content="noarchive"> <meta name="bingbot" content="noarchive">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark" /> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="{{ globals.theme_color }}" /> <meta name="theme-color" content="{{ globals.theme_color }}">
<meta name="msapplication-TileColor" content="{{ globals.theme_color }}"> <meta name="msapplication-TileColor" content="{{ globals.theme_color }}">
<meta name="fediverse:creator" content="{{ globals.mastodon }}" /> <meta name="fediverse:creator" content="{{ globals.mastodon }}">
<meta name="generator" content="{{ eleventy.generator }}" /> <meta name="generator" content="{{ eleventy.generator }}">
<link href="{{ globals.url }}/og/w50/{% appVersion %}{{ globals.metadata.open_graph_image_transparent }}" rel="icon" sizes="any" /> <link
<link href="{{ globals.url }}/og/w800/{% appVersion %}{{ globals.metadata.open_graph_image }}" rel="apple-touch-icon" /> href="{{ globals.url }}/og/w50/{% appVersion %}{{ globals.metadata.open_graph_image_transparent }}"
<link rel="alternate" href="{{ globals.url }}/feeds/posts.xml" title="Posts • {{ globals.site_name }}" /> rel="icon"
<link rel="alternate" href="{{ globals.url }}/feeds/links.xml" title="Links • {{ globals.site_name }}" type="application/rss+xml" /> sizes="any"
<link rel="alternate" href="{{ globals.url }}/feeds/movies.xml" title="Movies • {{ globals.site_name }}" type="application/rss+xml" /> >
<link rel="alternate" href="{{ globals.url }}/feeds/books.xml" title="Books • {{ globals.site_name }}" type="application/rss+xml" /> <link
href="{{ globals.url }}/og/w800/{% appVersion %}{{ globals.metadata.open_graph_image }}"
rel="apple-touch-icon"
>
<link
rel="alternate"
href="{{ globals.url }}/feeds/posts.xml"
title="Posts • {{ globals.site_name }}"
>
<link
rel="alternate"
href="{{ globals.url }}/feeds/links.xml"
title="Links • {{ globals.site_name }}"
type="application/rss+xml"
>
<link
rel="alternate"
href="{{ globals.url }}/feeds/movies.xml"
title="Movies • {{ globals.site_name }}"
type="application/rss+xml"
>
<link
rel="alternate"
href="{{ globals.url }}/feeds/books.xml"
title="Books • {{ globals.site_name }}"
type="application/rss+xml"
>
<link rel="blogroll" type="text/xml" href="/blogroll.opml"> <link rel="blogroll" type="text/xml" href="/blogroll.opml">

View file

@ -2,7 +2,7 @@
{%- assign source = page -%} {%- assign source = page -%}
{%- case schema -%} {%- case schema -%}
{%- when 'artist', 'genre', 'book', 'movie', 'show', 'tags' -%} {%- when 'artist', 'genre', 'book', 'movie', 'show', 'tags' -%}
{% render "dynamic/fetchers/{{ schema }}.php.liquid" %} {% render 'dynamic/fetchers/{{ schema }}.php.liquid' %}
{%- when 'blog' -%} {%- when 'blog' -%}
{%- assign source = post -%} {%- assign source = post -%}
{%- when 'music-index', 'music-week-artists' -%} {%- when 'music-index', 'music-week-artists' -%}
@ -18,7 +18,11 @@
{%- when 'books' -%} {%- when 'books' -%}
{%- assign source = books.all | filterBooksByStatus: 'started' | reverse | first -%} {%- assign source = books.all | filterBooksByStatus: 'started' | reverse | first -%}
{%- when 'reading-year' -%} {%- when 'reading-year' -%}
{%- assign title = 'Books • ' | append: year.value | append: ' • ' | append: globals.site_name -%} {%- assign title = 'Books • '
| append: year.value
| append: ' • '
| append: globals.site_name
-%}
{%- assign description = "Here's what I read in " | append: year.value | append: '.' -%} {%- assign description = "Here's what I read in " | append: year.value | append: '.' -%}
{%- assign bookYear = year.data | filterBooksByStatus: 'finished' | shuffleArray | first -%} {%- assign bookYear = year.data | filterBooksByStatus: 'finished' | shuffleArray | first -%}
{%- assign source = bookYear -%} {%- assign source = bookYear -%}
@ -35,29 +39,29 @@
{%- endcase %} {%- endcase %}
{%- assign meta = source | getMetadata: globals, page, title, description, schema -%} {%- assign meta = source | getMetadata: globals, page, title, description, schema -%}
{%- assign fullUrl = meta.url -%} {%- assign fullUrl = meta.url -%}
{%- assign oembedUrl = globals.url | append: "/oembed" | append: page.url -%} {%- assign oembedUrl = globals.url | append: '/oembed' | append: page.url -%}
{%- if type == 'dynamic' -%} {%- if type == 'dynamic' -%}
{% render "dynamic/metadata/index.php.liquid" {% render 'dynamic/metadata/index.php.liquid',
fullUrl: fullUrl, fullUrl: fullUrl,
oembedUrl: oembedUrl, oembedUrl: oembedUrl,
pageTitle: meta.title, pageTitle: meta.title,
pageDescription: meta.description, pageDescription: meta.description,
ogImage: meta.open_graph_image, ogImage: meta.open_graph_image,
globals: globals, globals: globals
%} %}
{%- else -%} {%- else -%}
{% render "static/metadata/static.liquid" {% render 'static/metadata/static.liquid',
fullUrl: fullUrl, fullUrl: fullUrl,
oembedUrl: oembedUrl, oembedUrl: oembedUrl,
pageTitle: meta.title, pageTitle: meta.title,
pageDescription: meta.description, pageDescription: meta.description,
ogImage: meta.open_graph_image, ogImage: meta.open_graph_image,
globals:globals, globals: globals
%} %}
{%- endif %} {%- endif %}
{% render "static/metadata/base.liquid" {% render 'static/metadata/base.liquid',
pageTitle: meta.title, pageTitle: meta.title,
globals: globals, globals: globals,
eleventy: eleventy, eleventy: eleventy,
appVersion:appVersion, appVersion: appVersion
%} %}

View file

@ -1,11 +1,11 @@
{%- assign title = pageTitle | escape -%} {%- assign title = pageTitle | escape -%}
{%- assign description = pageDescription | markdown | strip_html | htmlTruncate | escape -%} {%- assign description = pageDescription | markdown | strip_html | htmlTruncate | escape -%}
<title>{{ title }}</title> <title>{{ title }}</title>
<meta name="description" content="{{ description }}" /> <meta name="description" content="{{ description }}">
<meta property="og:title" content="{{ title }}" /> <meta property="og:title" content="{{ title }}">
<meta property="og:description" content="{{ description }}" /> <meta property="og:description" content="{{ description }}">
<meta property="og:type" content="{{ page.metadata.type | default: 'article' }}" /> <meta property="og:type" content="{{ page.metadata.type | default: 'article' }}">
<meta property="og:url" content="{{ fullUrl }}" /> <meta property="og:url" content="{{ fullUrl }}">
<meta property="og:image" content="{{ ogImage }}" /> <meta property="og:image" content="{{ ogImage }}">
<link rel="alternate" type="application/json+oembed" href="{{ oembedUrl }}" title="{{ title }}" /> <link rel="alternate" type="application/json+oembed" href="{{ oembedUrl }}" title="{{ title }}">
<link rel="canonical" href="{{ fullUrl }}" /> <link rel="canonical" href="{{ fullUrl }}">

View file

@ -21,7 +21,9 @@
<a <a
class="{% if link.section %}{{ link.section | downcase }} button {% endif %}{% if link.icon %}{{ link.icon | downcase }} icon {% endif %}" class="{% if link.section %}{{ link.section | downcase }} button {% endif %}{% if link.icon %}{{ link.icon | downcase }} icon {% endif %}"
href="{{ categoryUrl }}" href="{{ categoryUrl }}"
{% if isHttp -%} rel="me" {%- endif %} {% if isHttp -%}
rel="me"
{%- endif %}
title="{{ link.title }}" title="{{ link.title }}"
aria-label="{{ link.title }}" aria-label="{{ link.title }}"
> >

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