Compare commits
No commits in common. "555ba74bf6ddcedf2ac6935651c700b4a9491f01" and "49e21d574e4432fbb349e06c4d379901cf9c8248" have entirely different histories.
555ba74bf6
...
49e21d574e
114 changed files with 1202 additions and 1358 deletions
|
@ -14,7 +14,5 @@ vendor/
|
||||||
# env
|
# env
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# liquid with non-standard syntax
|
# php
|
||||||
**/*.php.liquid
|
*.php
|
||||||
**/feeds/*.liquid
|
|
||||||
**/meta/*.liquid
|
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
"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"]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/utils.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);
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
import inquirer from "inquirer";
|
|
||||||
import { searchItems, createItem } from "./client.js";
|
|
||||||
|
|
||||||
export const promptForTags = async () => {
|
|
||||||
const tagIds = [];
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { query } = await inquirer.prompt({
|
|
||||||
name: "query",
|
|
||||||
message: "🏷 Search for tags (or leave blank to finish):"
|
|
||||||
});
|
|
||||||
|
|
||||||
const trimmedQuery = query.trim();
|
|
||||||
if (!trimmedQuery) break;
|
|
||||||
|
|
||||||
const tags = await searchItems("tags", trimmedQuery);
|
|
||||||
|
|
||||||
if (!tags.length) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selected } = await inquirer.prompt({
|
|
||||||
type: "checkbox",
|
|
||||||
name: "selected",
|
|
||||||
message: "✔ Select tags to add:",
|
|
||||||
choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
|
|
||||||
});
|
|
||||||
|
|
||||||
tagIds.push(...selected);
|
|
||||||
|
|
||||||
const { again } = await inquirer.prompt({
|
|
||||||
type: "confirm",
|
|
||||||
name: "again",
|
|
||||||
message: "Search and select more tags?",
|
|
||||||
default: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!again) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...new Set(tagIds)];
|
|
||||||
};
|
|
|
@ -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();
|
||||||
|
|
14
cli/lib/handlers.js
Normal file
14
cli/lib/handlers.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export const handleExitError = (err, type = 'Unhandled error') => {
|
||||||
|
const isExit =
|
||||||
|
err?.name === 'ExitPromptError' ||
|
||||||
|
err?.code === 'ERR_CANCELED' ||
|
||||||
|
err?.message?.includes('SIGINT');
|
||||||
|
|
||||||
|
if (isExit) {
|
||||||
|
console.log('\n👋 Exiting. Cya!\n');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`❌ ${type}:`, err);
|
||||||
|
process.exit(1);
|
||||||
|
};
|
124
cli/lib/jobs.js
124
cli/lib/jobs.js
|
@ -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) })
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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?:\/\//, '') : '');
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -1,8 +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';
|
||||||
import { promptForTags } from "../directus/tagHelpers.js";
|
|
||||||
|
|
||||||
export const addLinkToShare = async () => {
|
export const addLinkToShare = async () => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
|
@ -11,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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,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,
|
||||||
|
@ -71,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;
|
||||||
|
@ -88,9 +87,45 @@ export const addLinkToShare = async () => {
|
||||||
author = response.author;
|
author = response.author;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagIds = await promptForTags();
|
let tagIds = [];
|
||||||
|
|
||||||
await createItem("links", {
|
while (true) {
|
||||||
|
const { query } = await inquirer.prompt({
|
||||||
|
name: 'query',
|
||||||
|
message: '🏷 Search for tags (or leave blank to finish):'
|
||||||
|
});
|
||||||
|
|
||||||
|
const trimmedQuery = query.trim();
|
||||||
|
if (!trimmedQuery) break;
|
||||||
|
|
||||||
|
const tags = await searchItems('tags', trimmedQuery);
|
||||||
|
|
||||||
|
if (!tags.length) {
|
||||||
|
console.warn(`⚠️ No tags found matching "${trimmedQuery}"`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selected } = await inquirer.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'selected',
|
||||||
|
message: '✔ Select tags to add:',
|
||||||
|
choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
|
||||||
|
});
|
||||||
|
|
||||||
|
tagIds.push(...selected);
|
||||||
|
|
||||||
|
const { again } = await inquirer.prompt({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'again',
|
||||||
|
message: 'Search and select more tags?',
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!again) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createItem('links', {
|
||||||
title,
|
title,
|
||||||
link,
|
link,
|
||||||
description,
|
description,
|
||||||
|
@ -99,5 +134,5 @@ export const addLinkToShare = async () => {
|
||||||
date: new Date().toISOString()
|
date: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("✅ Link created successfully.");
|
console.log('✅ Link created successfully.');
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +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';
|
||||||
import { promptForTags } from "../directus/tagHelpers.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 () => {
|
||||||
|
@ -21,50 +20,87 @@ 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
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tagIds = await promptForTags();
|
let tagIds = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { query } = await inquirer.prompt({
|
||||||
|
name: 'query',
|
||||||
|
message: '🏷 Search for tags (or leave blank to finish):'
|
||||||
|
});
|
||||||
|
const trimmedQuery = query.trim();
|
||||||
|
|
||||||
|
if (!trimmedQuery) break;
|
||||||
|
|
||||||
|
const tags = await searchItems('tags', trimmedQuery);
|
||||||
|
|
||||||
|
if (!tags.length) {
|
||||||
|
console.warn(`⚠️ No tags found matching "${trimmedQuery}"`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selected } = await inquirer.prompt({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'selected',
|
||||||
|
message: '✔ Select tags to add:',
|
||||||
|
choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
|
||||||
|
});
|
||||||
|
|
||||||
|
tagIds.push(...selected);
|
||||||
|
|
||||||
|
const { again } = await inquirer.prompt({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'again',
|
||||||
|
message: 'Search and select more tags?',
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!again) break;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -76,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,
|
||||||
|
@ -88,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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,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):`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,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,
|
||||||
|
@ -140,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,
|
||||||
|
@ -152,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.');
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 }))
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -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 (0–100):",
|
message: '📕 New progress percentage (0–100):',
|
||||||
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}%`);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
export const handleExitError = (err, type = "Unhandled error") => {
|
|
||||||
const isExit =
|
|
||||||
err?.name === "ExitPromptError" ||
|
|
||||||
err?.code === "ERR_CANCELED" ||
|
|
||||||
err?.message?.includes("SIGINT");
|
|
||||||
|
|
||||||
if (isExit) {
|
|
||||||
console.log("\n👋 Exiting. Cya!\n");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`❌ ${type}:`, err);
|
|
||||||
process.exit(1);
|
|
||||||
};
|
|
4
cli/package-lock.json
generated
4
cli/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "3.4.0",
|
"version": "3.2.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "3.4.0",
|
"version": "3.2.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@directus/sdk": "^19.1.0",
|
"@directus/sdk": "^19.1.0",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "3.4.0",
|
"version": "3.2.6",
|
||||||
"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": {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 = "&";
|
const replacement = '&';
|
||||||
|
|
||||||
return string.replace(pattern, replacement);
|
return string.replace(pattern, replacement);
|
||||||
},
|
},
|
||||||
replaceQuotes: (string) => {
|
replaceQuotes: (string) => {
|
||||||
if (!string) return "";
|
if (!string) return '';
|
||||||
return string.replace(/"/g, """).replace(/'/g, "'");
|
return string.replace(/"/g, '"');
|
||||||
},
|
},
|
||||||
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, '\\$&')
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}`;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
90
package-lock.json
generated
90
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "coryd.dev",
|
"name": "coryd.dev",
|
||||||
"version": "10.7.0",
|
"version": "10.6.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "coryd.dev",
|
"name": "coryd.dev",
|
||||||
"version": "10.7.0",
|
"version": "10.6.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minisearch": "^7.1.2",
|
"minisearch": "^7.1.2",
|
||||||
|
@ -16,7 +16,6 @@
|
||||||
"@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",
|
||||||
|
@ -236,9 +235,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@11ty/recursive-copy": {
|
"node_modules/@11ty/recursive-copy": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.1.tgz",
|
||||||
"integrity": "sha512-174nFXxL/6KcYbLYpra+q3nDbfKxLxRTNVY1atq2M1pYYiPfHse++3IFNl8mjPFsd7y2qQjxLORzIjHMjL3NDQ==",
|
"integrity": "sha512-Zsg1xgfdVTMKNPj9o4FZeYa73dFZRX856CL4LsmqPMvDr0TuIK4cH9CVWJyf0OkNmM8GmlibGX18fF0B75Rn1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -467,31 +466,6 @@
|
||||||
"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",
|
||||||
|
@ -1681,9 +1655,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.168",
|
"version": "1.5.167",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.168.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
|
||||||
"integrity": "sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==",
|
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
@ -2176,13 +2150,6 @@
|
||||||
"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",
|
||||||
|
@ -2438,13 +2405,6 @@
|
||||||
"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",
|
||||||
|
@ -2462,19 +2422,6 @@
|
||||||
"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",
|
||||||
|
@ -2591,17 +2538,6 @@
|
||||||
"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",
|
||||||
|
@ -3833,16 +3769,6 @@
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "coryd.dev",
|
"name": "coryd.dev",
|
||||||
"version": "10.7.0",
|
"version": "10.6.7",
|
||||||
"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,liquid}' && composer format:php && npm run format:sql",
|
"format": "npx prettier --write '**/*.{js,json,css,md}' && 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,liquid}": "prettier --write",
|
"*.{js,json,css,md}": "prettier --write",
|
||||||
"*.php": "composer format:php",
|
"*.php": "composer format:php",
|
||||||
"*.md": "markdownlint"
|
"*.md": "markdownlint"
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,6 @@
|
||||||
"@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",
|
||||||
|
|
|
@ -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('/')}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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: [] };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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, []);
|
||||||
|
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
|
|
||||||
?>
|
?>
|
||||||
{% render "static/blocks/banners/rss.liquid",
|
{% render "static/blocks/banners/rss.liquid",
|
||||||
url: "/feeds",
|
url:"/feeds",
|
||||||
text: "Subscribe to my posts, movies, books, links or activity feed(s)"
|
text:"Subscribe to my posts, movies, books, links or activity feed(s)"
|
||||||
%}
|
%}
|
||||||
|
|
|
@ -1,41 +1,29 @@
|
||||||
{% assign media = artists
|
{% assign media = artists | concat:books | concat:genres | concat:movies | concat:posts | concat:shows %}
|
||||||
| 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 = 'artists:headphones:music:Related artist(s):true,'
|
{% assign sections =
|
||||||
| append: 'books:books:books:Related book(s):true,'
|
"artists:headphones:music:Related artist(s):true," | append:
|
||||||
| append: 'genres:headphones:music:Related genre(s):false,'
|
"books:books:books:Related book(s):true," | append:
|
||||||
| append: 'movies:movie:movies:Related movie(s):true,'
|
"genres:headphones:music:Related genre(s):false," | append:
|
||||||
| append: 'posts:article:article:Related post(s):false,'
|
"movies:movie:movies:Related movie(s):true," | append:
|
||||||
| append: 'shows:device-tv-old:tv:Related show(s):true'
|
"posts:article:article:Related post(s):false," | append:
|
||||||
| split: ','
|
"shows:device-tv-old:tv:Related show(s):true"
|
||||||
%}
|
| 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 = null %}
|
{% assign items = nil %}
|
||||||
{% case key %}
|
{% case key %}
|
||||||
{% when 'artists' %}
|
{% when "artists" %} {% assign items = artists %}
|
||||||
{% assign items = artists %}
|
{% when "books" %} {% assign items = books %}
|
||||||
{% when 'books' %}
|
{% when "genres" %} {% assign items = genres %}
|
||||||
{% assign items = books %}
|
{% when "movies" %} {% assign items = movies %}
|
||||||
{% when 'genres' %}
|
{% when "posts" %} {% assign items = posts %}
|
||||||
{% assign items = genres %}
|
{% when "shows" %} {% assign items = shows %}
|
||||||
{% 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 }}">
|
||||||
|
@ -43,25 +31,27 @@
|
||||||
{{ 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', data: items, shape: shape %}
|
{% render "static/media/grid.liquid",
|
||||||
|
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 }} {{ item.total_plays | pluralize:"play" }})
|
||||||
{{ item.total_plays | pluralize: 'play' }})
|
{% elsif key == "books" %}
|
||||||
{% 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 %}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
<div class="banner calendar">
|
<div class="banner calendar">
|
||||||
<p>
|
<p>{% tablericon "calendar" %} <a href="{{ url }}">{{ text }}</a>.</p>
|
||||||
{% tablericon "calendar" %}
|
|
||||||
<a href="{{ url }}">{{ text }}</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
<div class="banner forgejo">
|
<div class="banner forgejo">
|
||||||
<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>
|
||||||
{% 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>
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
<div class="banner github">
|
<div class="banner github">
|
||||||
<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>
|
||||||
{% 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>
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
<div class="banner npm">
|
<div class="banner npm">
|
||||||
<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>
|
||||||
{% 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>
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
{%- if isOldPost -%}
|
{%- if isOldPost -%}
|
||||||
<div class="banner old-post">
|
<div class="banner old-post">
|
||||||
<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>
|
||||||
{% 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 -%}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
<div class="banner rss">
|
<div class="banner rss">
|
||||||
<p>
|
<p>{% tablericon "rss" %} <a href="{{ url }}">{{ text }}</a>.</p>
|
||||||
{% tablericon "rss" %}
|
|
||||||
<a href="{{ url }}">{{ text }}</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
<div class="banner warning">
|
<div class="banner warning">
|
||||||
<p>
|
<p>{% tablericon "alert-triangle" %} {{ text }}</p>
|
||||||
{% tablericon "alert-triangle" %}
|
|
||||||
{{ text }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,25 +5,15 @@
|
||||||
{{ label }}
|
{{ label }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endcapture -%}
|
{%- endcapture -%}
|
||||||
{% assign dialogId = id | default: 'dialog-controls' %}
|
{% assign dialogId = id | default:"dialog-controls" %}
|
||||||
<button
|
<button class="dialog-open client-side" aria-label="{{ label }}" data-dialog-trigger="{{ dialogId }}" data-dialog-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 %}
|
{% if dynamic %}data-dynamic="{{ dynamic }}"{% endif %}
|
||||||
data-dynamic="{{ dynamic }}"
|
{% if markdown %}data-markdown="{{ markdown }}"{% endif %}>
|
||||||
{% 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>
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
{{ 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="
|
sizes="(max-width: 450px) 256px,
|
||||||
(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"
|
||||||
|
|
|
@ -1,22 +1,41 @@
|
||||||
{%- 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', url: block.url, text: block.text %}
|
{% render "static/blocks/banners/calendar.liquid",
|
||||||
{%- when 'divider' -%}
|
url:block.url,
|
||||||
|
text:block.text
|
||||||
|
%}
|
||||||
|
{%- when "divider" -%}
|
||||||
{{ block.markup | markdown }}
|
{{ block.markup | markdown }}
|
||||||
{%- when 'forgejo_banner' -%}
|
{%- when "forgejo_banner" -%}
|
||||||
{% render 'static/blocks/banners/forgejo.liquid', url: block.url %}
|
{% render "static/blocks/banners/forgejo.liquid",
|
||||||
{%- when 'github_banner' -%}
|
url:block.url
|
||||||
{% render 'static/blocks/banners/github.liquid', url: block.url %}
|
%}
|
||||||
{%- when 'hero' -%}
|
{%- when "github_banner" -%}
|
||||||
{% render 'static/blocks/hero.liquid', globals: globals, image: block.image, alt: block.alt %}
|
{% render "static/blocks/banners/github.liquid",
|
||||||
{%- when 'markdown' -%}
|
url:block.url
|
||||||
|
%}
|
||||||
|
{%- 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', url: block.url, command: block.command %}
|
{% render "static/blocks/banners/npm.liquid",
|
||||||
{%- when 'rss_banner' -%}
|
url:block.url,
|
||||||
{% render 'static/blocks/banners/rss.liquid', url: block.url, text: block.text %}
|
command:block.command
|
||||||
{%- when 'youtube_player' -%}
|
%}
|
||||||
{% render 'static/blocks/youtube-player.liquid', url: block.url %}
|
{%- when "rss_banner" -%}
|
||||||
|
{% 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 -%}
|
||||||
|
|
|
@ -1,8 +1 @@
|
||||||
{% if tags %}
|
{% 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 %}
|
||||||
<p class="tags tags-small">
|
|
||||||
{%- for tag in tags %}
|
|
||||||
{% assign tagLowercase = tag | downcase -%}
|
|
||||||
<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>
|
|
||||||
{%- endfor -%}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
|
@ -1,7 +1 @@
|
||||||
<p>
|
<p><mark>{{ label }}</mark>{%- for tag in tags %} {% assign tagLowercase = tag.tag | downcase -%}<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>{%- endfor -%}</p>
|
||||||
<mark>{{ label }}</mark>
|
|
||||||
{%- for tag in tags %}
|
|
||||||
{% assign tagLowercase = tag.tag | downcase -%}
|
|
||||||
<a href="/tags/{{ tagLowercase | url_encode }}">#{{ tagLowercase }}</a>
|
|
||||||
{%- endfor -%}
|
|
||||||
</p>
|
|
||||||
|
|
|
@ -1,6 +1,2 @@
|
||||||
<script
|
<script type="module" src="/assets/scripts/components/youtube-video-element.js?v={% appVersion %}" defer></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>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<article class="intro">
|
<article class="intro">
|
||||||
{{ intro }}
|
{{ intro }}
|
||||||
{% render 'dynamic/media/now-playing.php.liquid', section: 'music' %}
|
{% render "dynamic/media/now-playing.php.liquid",
|
||||||
<hr>
|
section:"music"
|
||||||
|
%}
|
||||||
|
<hr />
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
{%- for item in items -%}
|
{%- for item in items -%}
|
||||||
<article class="{{ item.type }}">
|
<article class="{{ item.type }}">
|
||||||
<aside>
|
<aside>
|
||||||
{%- if item.featured -%}
|
{%- if item.featured -%}
|
||||||
{% 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,6 +49,8 @@
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</h3>
|
</h3>
|
||||||
{% render 'static/blocks/tags.liquid', tags: item.tags %}
|
{% render "static/blocks/tags.liquid",
|
||||||
</article>
|
tags:item.tags
|
||||||
|
%}
|
||||||
|
</article>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
<footer>
|
<footer>
|
||||||
{% render 'static/nav/menu.liquid', page: page, nav: nav.footer_icons, class: 'social' %}
|
{% render "static/nav/menu.liquid",
|
||||||
{% render 'static/nav/menu.liquid',
|
page:page,
|
||||||
page: page,
|
nav:nav.footer_icons
|
||||||
nav: nav.footer_text,
|
class:"social"
|
||||||
class: 'sub-pages',
|
%}
|
||||||
separator: true
|
{% render "static/nav/menu.liquid",
|
||||||
|
page:page,
|
||||||
|
nav:nav.footer_text
|
||||||
|
class:"sub-pages"
|
||||||
|
separator:true
|
||||||
%}
|
%}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -15,12 +15,20 @@
|
||||||
{%- 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', page: page, nav: nav.primary_icons, class: 'icons' %}
|
{% render "static/nav/menu.liquid",
|
||||||
|
page:page,
|
||||||
|
nav:nav.primary_icons
|
||||||
|
class:"icons"
|
||||||
|
%}
|
||||||
</section>
|
</section>
|
||||||
{% render 'static/nav/menu.liquid', page: page, nav: nav.primary, class: 'primary' %}
|
{% render "static/nav/menu.liquid",
|
||||||
|
page:page,
|
||||||
|
nav:nav.primary
|
||||||
|
class:"primary"
|
||||||
|
%}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<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 -%}
|
||||||
<a class="{{ item.type }}" href="{{ item.grid.url }}" title="{{ alt }}">
|
<a class="{{ item.type }}" href="{{ item.grid.url }}" title="{{ alt }}">
|
||||||
|
@ -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,4 +41,6 @@
|
||||||
</a>
|
</a>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</div>
|
</div>
|
||||||
{% render 'static/nav/paginator.liquid', pagination: pagination %}
|
{% render "static/nav/paginator.liquid",
|
||||||
|
pagination:pagination
|
||||||
|
%}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<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">
|
<span class="subheader">{{ item.chart.plays }} {{ playsLabel }}</span>
|
||||||
{{- item.chart.plays }}
|
|
||||||
{{ playsLabel -}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-item-progress">
|
<div class="chart-item-progress">
|
||||||
{% render 'static/media/progress-bar.liquid', percentage: item.chart.percentage %}
|
{% render "static/media/progress-bar.liquid",
|
||||||
|
percentage:item.chart.percentage
|
||||||
|
%}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
<div class="music-chart">
|
<div class="music-chart">
|
||||||
<ol type="1">
|
<ol type="1">
|
||||||
{%- 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', item: item %}
|
{% render "static/media/music/charts/item.liquid",
|
||||||
|
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', item: item %}
|
{% render "static/media/music/charts/item.liquid",
|
||||||
|
item:item
|
||||||
|
%}
|
||||||
</li>
|
</li>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
{% render 'static/nav/paginator.liquid', pagination: pagination %}
|
{% render "static/nav/paginator.liquid",
|
||||||
|
pagination:pagination
|
||||||
|
%}
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
{{ album.table.title }}
|
{{ album.table.title }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td><a href="{{ album.table.url }}">{{ album.table.artist }}</a></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>
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
<a href="{{ artist.table.url }}">{{ artist.table.title }}</a>
|
<a href="{{ artist.table.url }}">{{ artist.table.title }}</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{{ artist.table.emoji }} <a href="{{ artist.table.genre_url }}">{{ artist.table.genre }}</a></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 %}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{%- if percentage -%}
|
{%- if percentage -%}
|
||||||
<progress value="{{ percentage }}" max="100">{{ percentage }}%</progress>
|
<progress value="{{ percentage }}" max="100">{{ percentage }}%</progress>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
({{ 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
|
||||||
%}
|
%}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue