Compare commits

..

No commits in common. "e36dab2e5733f1e907d3fe9744b87e26cb392bdb" and "81412117d8aeab46025652ade7a483b182e8d971" have entirely different histories.

11 changed files with 343 additions and 440 deletions

View file

@ -1,40 +1,31 @@
const { DateTime } = require("luxon"); const { DateTime } = require("luxon");
module.exports = function (eleventyConfig, { TIME_ZONE }) { module.exports = function (eleventyConfig, { TIME_ZONE }) {
eleventyConfig.addDateParsing(function (dateValue) { eleventyConfig.addDateParsing(function (dateValue) {
// i know this is a deranged solution. sorry LOL // i know this is a deranged solution. sorry LOL
let localDate; let localDate;
try { try {
if (dateValue instanceof Date && !isNaN(dateValue)) { if (dateValue instanceof Date && !isNaN(dateValue)) {
// handle filename dates (ie 2025-10-18-post.md) // handle filename dates (ie 2025-10-18-post.md)
localDate = DateTime.fromJSDate(dateValue, { zone: "utc" }) localDate = DateTime.fromJSDate(dateValue, { zone: "utc" })
.setZone(TIME_ZONE) .setZone(TIME_ZONE)
.startOf("day"); // Set to midnight in America/Santiago .startOf("day"); // Set to midnight in America/Santiago
} else if (typeof dateValue === "string" && dateValue) { } else if (typeof dateValue === "string" && dateValue) {
// handle string dates (ie from front matter, if used) // handle string dates (ie from front matter, if used)
localDate = DateTime.fromISO(dateValue, { zone: TIME_ZONE }).startOf( localDate = DateTime.fromISO(dateValue, { zone: TIME_ZONE }).startOf("day");
"day", } else {
); // handle invalid input
} else { console.warn(`Invalid date value: ${dateValue} for ${this.page.inputPath}`);
// handle invalid input localDate = DateTime.now().setZone(TIME_ZONE).startOf("day");
console.warn( }
`Invalid date value: ${dateValue} for ${this.page.inputPath}`, if (!localDate || localDate.isValid === false) {
); throw new Error(`Invalid date value (${dateValue}) for ${this.page.inputPath}: ${localDate?.invalidReason || "Unknown"}`);
localDate = DateTime.now().setZone(TIME_ZONE).startOf("day"); }
} return localDate.toJSDate();
if (!localDate || localDate.isValid === false) { } catch (error) {
throw new Error( console.error(`Date parsing error for ${this.page.inputPath}:`, error.message);
`Invalid date value (${dateValue}) for ${this.page.inputPath}: ${localDate?.invalidReason || "Unknown"}`, // fallback to current date in TIME_ZONE
); return DateTime.now().setZone(TIME_ZONE).startOf("day").toJSDate();
} }
return localDate.toJSDate(); });
} catch (error) {
console.error(
`Date parsing error for ${this.page.inputPath}:`,
error.message,
);
// fallback to current date in TIME_ZONE
return DateTime.now().setZone(TIME_ZONE).startOf("day").toJSDate();
}
});
}; };

View file

@ -1,235 +1,158 @@
const { DateTime } = require("luxon"); const { DateTime } = require("luxon");
module.exports = function (eleventyConfig, { TIME_ZONE, defaultLanguage }) { module.exports = function (eleventyConfig, { TIME_ZONE, defaultLanguage }) {
// {{ post.date | date("dd/MM/yyyy") }} -> 18/10/2025 // {{ post.date | date("dd/MM/yyyy") }} -> 18/10/2025
eleventyConfig.addFilter("date", function (dateObj, format = "dd/MM/yyyy") { eleventyConfig.addFilter("date", function (dateObj, format = "dd/MM/yyyy") {
let dt = dateObj; let dt = dateObj;
// handle string dates // handle string dates
if (typeof dateObj === "string") { if (typeof dateObj === "string") {
dt = DateTime.fromISO(dateObj, { zone: TIME_ZONE }).toJSDate(); dt = DateTime.fromISO(dateObj, { zone: TIME_ZONE }).toJSDate();
}
// handle DateTime objects (from addDateParsing)
if (dateObj instanceof DateTime) {
dt = dateObj.toJSDate();
}
// check dt as valid Date object
if (!(dt instanceof Date) || isNaN(dt)) {
console.log("Invalid date input:", dateObj);
return "";
}
// format in TIME_ZONE
const formatted = DateTime.fromJSDate(dt, { zone: TIME_ZONE }).toFormat(
format,
);
console.log(
"Date input:",
dt,
"Formatted:",
formatted,
"Timezone:",
TIME_ZONE,
);
return formatted;
});
// filters collections by current language
eleventyConfig.addFilter("i18n_filter", function (collection, limit = null) {
const lang = this.page.lang; // access page.lang from context
let filtered = collection.filter((item) => item.data.lang === lang);
if (limit !== null) {
filtered = filtered.slice(0, limit);
}
return filtered;
});
// takes all collections and returns only the tags not matched
// key-objects so may repurpose under different name
eleventyConfig.addFilter(
"exclude_collections",
function (collections, ...keysToExclude) {
if (!collections || typeof collections !== "object") {
console.warn(
"[exclude_collections] Invalid collections input:",
collections,
);
return collections;
}
const result = {};
Object.keys(collections).forEach((key) => {
if (!keysToExclude.includes(key)) {
result[key] = collections[key];
} }
}); // handle DateTime objects (from addDateParsing)
if (dateObj instanceof DateTime) {
if ( dt = dateObj.toJSDate();
process.env.LOCALE_URL_DEBUG === "1" ||
process.env.LOCALE_URL_DEBUG === "true"
) {
console.warn("[excludeCollections] Excluded keys:", keysToExclude);
console.warn(
"[excludeCollections] Resulting keys:",
Object.keys(result),
);
}
return result;
},
);
const LOCALE_URL_DEBUG =
process.env.LOCALE_URL_DEBUG === "1" ||
process.env.LOCALE_URL_DEBUG === "true";
// locale_url replacement that uses pre-compile filesystem
eleventyConfig.addNunjucksFilter(
"locale_url_resolve",
function (targetUrl, desiredLocale) {
const ctx = this && this.ctx ? this.ctx : {};
const collections = ctx.collections ? ctx.collections : {};
const all = collections.all || [];
if (!targetUrl || typeof targetUrl !== "string") return targetUrl;
if (!targetUrl.startsWith("/")) return targetUrl; // external or relative link -> leave as is
// determine locale to resolve to
const pageLang =
desiredLocale ||
(ctx.page && ctx.page.lang) ||
ctx.locale ||
defaultLanguage;
const LOCALE_URL_DEBUG =
process.env.LOCALE_URL_DEBUG === "1" ||
process.env.LOCALE_URL_DEBUG === "true";
if (LOCALE_URL_DEBUG) {
console.warn(
`[locale_url_resolve] resolving targetUrl="${targetUrl}" desiredLocale="${desiredLocale}" pageLang="${pageLang}"`,
);
}
// special case for homepage
if (targetUrl === "/" || targetUrl === "/index/") {
if (pageLang === defaultLanguage) {
if (LOCALE_URL_DEBUG)
console.warn(
`[locale_url_resolve] homepage, default language (${defaultLanguage}) -> "/"`,
);
return "/";
} }
const homepageUrl = `/${pageLang}/`; // check dt as valid Date object
if (LOCALE_URL_DEBUG) if (!(dt instanceof Date) || isNaN(dt)) {
console.warn( console.log("Invalid date input:", dateObj);
`[locale_url_resolve] homepage, language (${pageLang}) -> "${homepageUrl}"`, return "";
);
return homepageUrl;
}
// normalize targetUrl to include trailing slash for comparison
const normUrl = targetUrl.endsWith("/") ? targetUrl : `${targetUrl}/`;
// try to find the canonical (default language) page corresponding to targetUrl
let canonical = all.find((p) => {
return (
(p.url === normUrl || p.url === targetUrl) &&
p.data &&
p.data.lang === defaultLanguage
);
});
// if not found, try to find any page with that url (maybe targetUrl already localized)
if (!canonical) {
canonical = all.find((p) => p.url === normUrl || p.url === targetUrl);
}
if (LOCALE_URL_DEBUG) {
if (canonical) {
const cs =
canonical.page && canonical.page.filePathStem
? canonical.page.filePathStem
: "(no stem)";
const clang =
canonical.data && canonical.data.lang
? canonical.data.lang
: "(no lang)";
console.warn(
`[locale_url_resolve] canonical found: url="${canonical.url}" filePathStem="${cs}" lang="${clang}"`,
);
} else {
console.warn(
`[locale_url_resolve] canonical NOT found for targetUrl="${targetUrl}". Will fallback to prefixed URL.`,
);
} }
} // format in TIME_ZONE
const formatted = DateTime.fromJSDate(dt, { zone: TIME_ZONE })
.toFormat(format);
console.log("Date input:", dt, "Formatted:", formatted, "Timezone:", TIME_ZONE);
return formatted;
});
// if cannot find canonical page, fall back to a prefixed URL (best effort) // filters collections by current language
if (!canonical) { eleventyConfig.addFilter("i18n_filter", function (collection, limit = null) {
const fallback = `/${pageLang}${targetUrl}`.replace(/\/{2,}/g, "/"); const lang = this.page.lang; // access page.lang from context
if (LOCALE_URL_DEBUG) let filtered = collection.filter(item => item.data.lang === lang);
console.warn(`[locale_url_resolve] fallback -> "${fallback}"`); if (limit !== null) {
return fallback; filtered = filtered.slice(0, limit);
} }
return filtered;
});
// determine canonical filePathStem (the source input path without extension) // takes all collections and returns only the tags not matched
const canonicalLang = // key-objects so may repurpose under different name
canonical.data && canonical.data.lang eleventyConfig.addFilter("exclude_collections", function (collections, ...keysToExclude) {
? canonical.data.lang if (!collections || typeof collections !== "object") {
: defaultLanguage; console.warn("[exclude_collections] Invalid collections input:", collections);
const canonicalStem = return collections;
canonical.page && canonical.page.filePathStem }
? canonical.page.filePathStem
: "";
// remove the canonical lang prefix from the stem to create a "key" to match across locales. const result = {};
// ie "/en/about" -> "/about", "/es/about" -> "/about" Object.keys(collections).forEach(key => {
const key = canonicalStem if (!keysToExclude.includes(key)) {
.replace(new RegExp(`^/${canonicalLang}`), "") result[key] = collections[key];
.replace(/^\/+/, ""); }
});
if (LOCALE_URL_DEBUG) { if (process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true") {
console.warn( console.warn("[excludeCollections] Excluded keys:", keysToExclude);
`[locale_url_resolve] canonicalLang="${canonicalLang}" canonicalStem="${canonicalStem}" key="${key}"`, console.warn("[excludeCollections] Resulting keys:", Object.keys(result));
); }
}
// find the localized page whose filePathStem ends with that key and whose lang matches pageLang. return result;
const localized = all.find((p) => { });
const pLang = p.data && p.data.lang;
const pStem = p.page && p.page.filePathStem ? p.page.filePathStem : "";
// ensure pLang exists
if (!pLang) return false;
return pLang === pageLang && pStem.endsWith(key);
});
if (localized && localized.url) { const LOCALE_URL_DEBUG = process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true";
// locale_url replacement that uses pre-compile filesystem
eleventyConfig.addNunjucksFilter("locale_url_resolve", function (targetUrl, desiredLocale) {
const ctx = (this && this.ctx) ? this.ctx : {};
const collections = (ctx.collections) ? ctx.collections : {};
const all = collections.all || [];
if (!targetUrl || typeof targetUrl !== "string") return targetUrl;
if (!targetUrl.startsWith("/")) return targetUrl; // external or relative link -> leave as is
// determine locale to resolve to
const pageLang = desiredLocale || (ctx.page && ctx.page.lang) || ctx.locale || defaultLanguage;
const LOCALE_URL_DEBUG = process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true";
if (LOCALE_URL_DEBUG) { if (LOCALE_URL_DEBUG) {
const ls = console.warn(`[locale_url_resolve] resolving targetUrl="${targetUrl}" desiredLocale="${desiredLocale}" pageLang="${pageLang}"`);
localized.page && localized.page.filePathStem
? localized.page.filePathStem
: "(no stem)";
console.warn(
`[locale_url_resolve] localized found: url="${localized.url}" filePathStem="${ls}" lang="${localized.data.lang}"`,
);
} }
return localized.url;
}
// fallback to prefixed URL // special case for homepage
const fallback2 = `/${pageLang}${targetUrl}`.replace(/\/{2,}/g, "/"); if (targetUrl === "/" || targetUrl === "/index/") {
if (LOCALE_URL_DEBUG) if (pageLang === defaultLanguage) {
console.warn( if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] homepage, default language (${defaultLanguage}) -> "/"`);
`[locale_url_resolve] localized NOT found for key="${key}" — fallback -> "${fallback2}"`, return "/";
); }
return fallback2; const homepageUrl = `/${pageLang}/`;
}, if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] homepage, language (${pageLang}) -> "${homepageUrl}"`);
); return homepageUrl;
}
// generic exclude filter // normalize targetUrl to include trailing slash for comparison
eleventyConfig.addFilter("exclude", (array, ...values) => { const normUrl = targetUrl.endsWith("/") ? targetUrl : `${targetUrl}/`;
return array.filter((item) => !values.includes(item));
});
// turn on disabled nunjucks filters // try to find the canonical (default language) page corresponding to targetUrl
eleventyConfig.addFilter("keys", (obj) => Object.keys(obj)); let canonical = all.find(p => {
eleventyConfig.addFilter("values", (obj) => Object.values(obj)); return (p.url === normUrl || p.url === targetUrl) && p.data && p.data.lang === defaultLanguage;
});
// if not found, try to find any page with that url (maybe targetUrl already localized)
if (!canonical) {
canonical = all.find(p => (p.url === normUrl || p.url === targetUrl));
}
if (LOCALE_URL_DEBUG) {
if (canonical) {
const cs = (canonical.page && canonical.page.filePathStem) ? canonical.page.filePathStem : "(no stem)";
const clang = (canonical.data && canonical.data.lang) ? canonical.data.lang : "(no lang)";
console.warn(`[locale_url_resolve] canonical found: url="${canonical.url}" filePathStem="${cs}" lang="${clang}"`);
} else {
console.warn(`[locale_url_resolve] canonical NOT found for targetUrl="${targetUrl}". Will fallback to prefixed URL.`);
}
}
// if cannot find canonical page, fall back to a prefixed URL (best effort)
if (!canonical) {
const fallback = `/${pageLang}${targetUrl}`.replace(/\/{2,}/g, "/");
if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] fallback -> "${fallback}"`);
return fallback;
}
// determine canonical filePathStem (the source input path without extension)
const canonicalLang = canonical.data && canonical.data.lang ? canonical.data.lang : defaultLanguage;
const canonicalStem = (canonical.page && canonical.page.filePathStem) ? canonical.page.filePathStem : "";
// remove the canonical lang prefix from the stem to create a "key" to match across locales.
// ie "/en/about" -> "/about", "/es/about" -> "/about"
const key = canonicalStem.replace(new RegExp(`^/${canonicalLang}`), "").replace(/^\/+/, "");
if (LOCALE_URL_DEBUG) {
console.warn(`[locale_url_resolve] canonicalLang="${canonicalLang}" canonicalStem="${canonicalStem}" key="${key}"`);
}
// find the localized page whose filePathStem ends with that key and whose lang matches pageLang.
const localized = all.find(p => {
const pLang = p.data && p.data.lang;
const pStem = (p.page && p.page.filePathStem) ? p.page.filePathStem : "";
// be defensive: ensure pLang exists
if (!pLang) return false;
return pLang === pageLang && pStem.endsWith(key);
});
if (localized && localized.url) {
if (LOCALE_URL_DEBUG) {
const ls = (localized.page && localized.page.filePathStem) ? localized.page.filePathStem : "(no stem)";
console.warn(`[locale_url_resolve] localized found: url="${localized.url}" filePathStem="${ls}" lang="${localized.data.lang}"`);
}
return localized.url;
}
// fallback to prefixed URL
const fallback2 = `/${pageLang}${targetUrl}`.replace(/\/{2,}/g, "/");
if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] localized NOT found for key="${key}" — fallback -> "${fallback2}"`);
return fallback2;
});
// turn on disabled nunjucks filters
eleventyConfig.addFilter("keys", obj => Object.keys(obj));
eleventyConfig.addFilter("values", obj => Object.values(obj));
}; };

View file

@ -6,26 +6,26 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
module.exports = function (eleventyConfig, { defaultLanguage }) { module.exports = function (eleventyConfig, { defaultLanguage }) {
// load translations // load translations
const translationsToml = fs.readFileSync( const translationsToml = fs.readFileSync(
path.join(__dirname, "..", "_data", "locale.toml"), path.join(__dirname, "..", "_data", "locale.toml"),
"utf-8", "utf-8"
); );
const translations = toml.parse(translationsToml); const translations = toml.parse(translationsToml);
// plugins // plugins
eleventyConfig.addPlugin(eleventySass); eleventyConfig.addPlugin(eleventySass);
eleventyConfig.addPlugin(I18nPlugin, { eleventyConfig.addPlugin(I18nPlugin, {
defaultLanguage, defaultLanguage,
errorMode: "allow-fallback", errorMode: "allow-fallback",
}); });
eleventyConfig.addPlugin(i18n, { eleventyConfig.addPlugin(i18n, {
translations, translations,
fallbackLocales: { fallbackLocales: {
"*": "en", "*": "en",
}, }
}); });
// data extensions // data extensions
eleventyConfig.addDataExtension("toml", (contents) => toml.parse(contents)); eleventyConfig.addDataExtension("toml", (contents) => toml.parse(contents));
}; };

View file

@ -1,33 +1,30 @@
const seperator = { const seperator = {
start: "<!-- excerpt start -->", start: '<!-- excerpt start -->',
end: "<!-- excerpt end -->", end: '<!-- excerpt end -->',
total: "<!-- excerpt -->", total: '<!-- excerpt -->'
}; };
module.exports = function (eleventyConfig) { module.exports = function (eleventyConfig) {
// excerpt shortcode for feed layouts // excerpt shortcode for feed layouts
// taken from https://github.com/brob/eleventy-plugin-blog-tools // taken from https://github.com/brob/eleventy-plugin-blog-tools
eleventyConfig.addShortcode("excerpt", function (article) { eleventyConfig.addShortcode("excerpt", function (article) {
let excerpt = article.data.excerpt ? `<p>${article.data.excerpt}</p>` : ""; let excerpt = article.data.excerpt ? `<p>${article.data.excerpt}</p>` : "";
const articleContent = article.templateContent; const articleContent = article.templateContent;
let startPosition = articleContent.toLowerCase().indexOf(seperator.start); let startPosition = articleContent.toLowerCase().indexOf(seperator.start);
let endPosition = articleContent.toLowerCase().indexOf(seperator.end); let endPosition = articleContent.toLowerCase().indexOf(seperator.end);
let totalPosition = articleContent.toLowerCase().indexOf(seperator.total); let totalPosition = articleContent.toLowerCase().indexOf(seperator.total)
if (totalPosition !== -1) { if (totalPosition !== -1) {
excerpt = articleContent.substring(0, totalPosition); excerpt = articleContent.substring(0, totalPosition);
} else if (startPosition !== -1 && endPosition !== -1) { } else if (startPosition !== -1 && endPosition !== -1) {
excerpt = articleContent.substring( excerpt = articleContent.substring(startPosition + seperator.start.length, endPosition);
startPosition + seperator.start.length, } else if (!article.data.excerpt) {
endPosition, let startPosition = articleContent.toLowerCase().indexOf('<p>');
); let endPosition = articleContent.toLowerCase().indexOf('</p>');
} else if (!article.data.excerpt) {
let startPosition = articleContent.toLowerCase().indexOf("<p>");
let endPosition = articleContent.toLowerCase().indexOf("</p>");
excerpt = articleContent.substring(startPosition + 3, endPosition); excerpt = articleContent.substring(startPosition + 3, endPosition);
} }
return excerpt; return excerpt
}); });
}; };

View file

@ -1,6 +1,6 @@
[buttons] [buttons]
people = [ people = [
{ image = "soniweb", name = "soniweb [en]", link = "https://soni.nekoweb.org/" }, { image = "soniweb", name = "soniweb [en]", link = "https://soniweb.org/" },
{ image = "https://vanityruins.neocities.org/8831/8831.gif", name = "vanityruins [en]", link = "https://vanityruins.neocities.org" }, { image = "https://vanityruins.neocities.org/8831/8831.gif", name = "vanityruins [en]", link = "https://vanityruins.neocities.org" },
{ image = "https://saint-images.neocities.org/images/buttons/me-zanarkand.png", name = "saint-images [en]", link = "https://saint-images.neocities.org"}, { image = "https://saint-images.neocities.org/images/buttons/me-zanarkand.png", name = "saint-images [en]", link = "https://saint-images.neocities.org"},
{ image = "cidoku", name = "Cidoku [es] [en]", link = "https://cidoku.net" }, { image = "cidoku", name = "Cidoku [es] [en]", link = "https://cidoku.net" },
@ -15,3 +15,4 @@ misc = [
{ image = "4everfriend", alt = "4everfriend!!" }, { image = "4everfriend", alt = "4everfriend!!" },
{ image = "cssisdifficult", alt = "CSS is very difficult..." } { image = "cssisdifficult", alt = "CSS is very difficult..." }
] ]

View file

@ -8,34 +8,31 @@ const addPlugins = require("./_config/plugins");
const addShortcodes = require("./_config/shortcodes"); const addShortcodes = require("./_config/shortcodes");
module.exports = function (eleventyConfig) { module.exports = function (eleventyConfig) {
let siteData; let siteData;
const siteToml = fs.readFileSync( const siteToml = fs.readFileSync(
path.join(__dirname, "_data", "site.toml"), path.join(__dirname, "_data", "site.toml"),
"utf-8", "utf-8"
); );
siteData = toml.parse(siteToml); siteData = toml.parse(siteToml);
eleventyConfig.addGlobalData("site", siteData); eleventyConfig.addGlobalData("site", siteData);
eleventyConfig.setLayoutsDirectory("_layouts"); eleventyConfig.setLayoutsDirectory("_layouts");
eleventyConfig.addPassthroughCopy("img"); eleventyConfig.addPassthroughCopy("img");
eleventyConfig.addPassthroughCopy("css/fonts"); eleventyConfig.addPassthroughCopy("css/fonts");
eleventyConfig.addPassthroughCopy("js"); eleventyConfig.addPassthroughCopy("js");
eleventyConfig.addPassthroughCopy("LICENSE.txt"); eleventyConfig.addPassthroughCopy("LICENSE.txt");
eleventyConfig.addPassthroughCopy("robots.txt"); eleventyConfig.addPassthroughCopy("robots.txt");
eleventyConfig.addPassthroughCopy("roms"); eleventyConfig.addPassthroughCopy("roms");
addDateParsing(eleventyConfig, { TIME_ZONE: siteData.timezone }); addDateParsing(eleventyConfig, { TIME_ZONE: siteData.timezone });
addFilters(eleventyConfig, { addFilters(eleventyConfig, { TIME_ZONE: siteData.timezone, defaultLanguage: siteData.default_language });
TIME_ZONE: siteData.timezone, addPlugins(eleventyConfig, { defaultLanguage: siteData.default_language });
defaultLanguage: siteData.default_language, addShortcodes(eleventyConfig);
});
addPlugins(eleventyConfig, { defaultLanguage: siteData.default_language });
addShortcodes(eleventyConfig);
return { return {
markdownTemplateEngine: "njk", markdownTemplateEngine: "njk",
htmlTemplateEngine: "njk", htmlTemplateEngine: "njk"
}; }
}; };

View file

@ -1,32 +1,30 @@
module.exports = { module.exports = {
eleventyComputed: { eleventyComputed: {
// TODO: handle titles as slugs instead of filenames // TODO: handle titles as slugs instead of filenames
permalink: (data) => { permalink: (data) => {
// get the file path stem ie "/en/blog/2025/2025-10-18-my-post" // get the file path stem ie "/en/blog/2025/2025-10-18-my-post"
let stem = data.page.filePathStem; let stem = data.page.filePathStem;
// strip the leading /en/ prefix // strip the leading /en/ prefix
if (stem.startsWith("/en/")) { if (stem.startsWith("/en/")) {
stem = stem.replace(/^\/en/, ""); stem = stem.replace(/^\/en/, "");
} }
// for blog posts under /blog/[year]/[year-month-day]-blogpost // for blog posts under /blog/[year]/[year-month-day]-blogpost
// extract the year and the blog post slug (remove date prefix) // extract the year and the blog post slug (remove date prefix)
const blogPostMatch = stem.match( const blogPostMatch = stem.match(/^\/blog\/(\d{4})\/\d{4}-\d{2}-\d{2}-(.+)$/);
/^\/blog\/(\d{4})\/\d{4}-\d{2}-\d{2}-(.+)$/, if (blogPostMatch) {
); const [, year, slug] = blogPostMatch;
if (blogPostMatch) { return `/blog/${year}/${slug}/index.html`; // ie /blog/2025/my-post/index.html
const [, year, slug] = blogPostMatch; }
return `/blog/${year}/${slug}/index.html`; // ie /blog/2025/my-post/index.html
}
// fallback for index or other pages under /blog // fallback for index or other pages under /blog
if (stem.endsWith("/index")) { if (stem.endsWith("/index")) {
return `${stem}.html`; // ie /blog/index.html return `${stem}.html`; // ie /blog/index.html
} }
// default for non-index, non-blog-post pages // default for non-index, non-blog-post pages
return `${stem}/index.html`; return `${stem}/index.html`;
}, }
}, }
}; };

View file

@ -1,21 +1,21 @@
module.exports = { module.exports = {
lang: "en", lang: "en",
permalink: (data) => { permalink: (data) => {
// data.page.filePathStem: e.g., "/en/index", "/en/blog/index", "/en/blog/test-post" // data.page.filePathStem: e.g., "/en/index", "/en/blog/index", "/en/blog/test-post"
let stem = data.page.filePathStem; let stem = data.page.filePathStem;
// strip the leading /en/ prefix // strip the leading /en/ prefix
if (stem.startsWith("/en/")) { if (stem.startsWith("/en/")) {
stem = stem.replace(/^\/en/, ""); stem = stem.replace(/^\/en/, "");
}
// handle index files: If ends with /index, just use stem + /index.html → e.g., /blog/index.html
// this avoids appending extra /index.html if already ending in /index
if (stem.endsWith("/index")) {
return `${stem}.html`; // /blog/index.html
}
// for non-index files: append /index.html for pretty URLs (e.g., /blog/test-post → /blog/test-post/index.html, URL /blog/test-post/)
return `${stem}/index.html`;
} }
};
// handle index files: If ends with /index, just use stem + /index.html → e.g., /blog/index.html
// this avoids appending extra /index.html if already ending in /index
if (stem.endsWith("/index")) {
return `${stem}.html`; // /blog/index.html
}
// for non-index files: append /index.html for pretty URLs (e.g., /blog/test-post → /blog/test-post/index.html, URL /blog/test-post/)
return `${stem}/index.html`;
},
};

View file

@ -1,36 +1,34 @@
module.exports = { module.exports = {
eleventyComputed: { eleventyComputed: {
permalink: function (data) { permalink: function (data) {
// get the file path stem // get the file path stem
let stem = data.page.filePathStem; let stem = data.page.filePathStem;
// extract the year from the path // extract the year from the path
const yearMatch = stem.match(/^\/es\/blog\/(\d{4})\//); const yearMatch = stem.match(/^\/es\/blog\/(\d{4})\//);
if (yearMatch) { if (yearMatch) {
const year = yearMatch[1]; const year = yearMatch[1];
// check for slug_override first // check for slug_override first
if (data.slug_override) { if (data.slug_override) {
return `/es/blog/${year}/${this.slugify(data.slug_override)}/index.html`; return `/es/blog/${year}/${this.slugify(data.slug_override)}/index.html`;
}
// handle blog posts with date prefix: /es/blog/[year]/[year-month-day]-slug
const blogPostMatch = stem.match(/^\/es\/blog\/(\d{4})\/\d{4}-\d{2}-\d{2}-(.+)$/);
if (blogPostMatch) {
const [, , slug] = blogPostMatch;
return `/es/blog/${year}/${this.slugify(slug)}/index.html`;
}
}
// handle index pages or other non-blog-post pages
if (stem.endsWith("/index")) {
return `${stem}.html`; // ie /es/blog/index.html
}
// default for other pages under /es/blog/
return `${stem}/index.html`; // ie /es/other-page/index.html
} }
}
// handle blog posts with date prefix: /es/blog/[year]/[year-month-day]-slug };
const blogPostMatch = stem.match(
/^\/es\/blog\/(\d{4})\/\d{4}-\d{2}-\d{2}-(.+)$/,
);
if (blogPostMatch) {
const [, , slug] = blogPostMatch;
return `/es/blog/${year}/${this.slugify(slug)}/index.html`;
}
}
// handle index pages or other non-blog-post pages
if (stem.endsWith("/index")) {
return `${stem}.html`; // ie /es/blog/index.html
}
// default for other pages under /es/blog/
return `${stem}/index.html`; // ie /es/other-page/index.html
},
},
};

View file

@ -1,19 +1,19 @@
module.exports = { module.exports = {
lang: "es", lang: 'es',
permalink: function (data) { permalink: function (data) {
let stem = data.page.filePathStem; let stem = data.page.filePathStem;
// handle slug_override // handle slug_override
if (data.slug_override) { if (data.slug_override) {
return `/${data.lang}/${this.slugify(data.slug_override)}/`; return `/${data.lang}/${this.slugify(data.slug_override)}/`;
}
// handle index pages
if (stem.endsWith("/index")) {
return `${stem}.html`; // e.g., /es/index.html
}
// default for other pages
return `${stem}/index.html`; // e.g., /es/about/index.html
} }
};
// handle index pages
if (stem.endsWith("/index")) {
return `${stem}.html`; // e.g., /es/index.html
}
// default for other pages
return `${stem}/index.html`; // e.g., /es/about/index.html
},
};

View file

@ -1,44 +1,42 @@
// this script is under the MIT license (https://max.nekoweb.org/resources/license.txt) // this script is under the MIT license (https://max.nekoweb.org/resources/license.txt)
const USERNAME = "kaaisudev"; // Put your LastFM username here const USERNAME = "kaaisudev"; // Put your LastFM username here
const BASE_URL = `https://lastfm-last-played.biancarosa.com.br/${USERNAME}/latest-song`; const BASE_URL = `https://lastfm-last-played.biancarosa.com.br/${USERNAME}/latest-song`;
const getTrack = async () => { const getTrack = async () => {
const request = await fetch(BASE_URL); const request = await fetch(BASE_URL);
const json = await request.json(); const json = await request.json();
let status; let status
let isPlaying = json.track["@attr"]?.nowplaying || false; let isPlaying = json.track['@attr']?.nowplaying || false;
if (!isPlaying) { if(!isPlaying) {
// Trigger if a song isn't playing // Trigger if a song isn't playing
return; return;
} else { } else {
// Trigger if a song is playing // Trigger if a song is playing
} }
// Values: // Values:
// COVER IMAGE: json.track.image[1]['#text'] // COVER IMAGE: json.track.image[1]['#text']
// TITLE: json.track.name // TITLE: json.track.name
// ARTIST: json.track.artist['#text'] // ARTIST: json.track.artist['#text']
document.getElementById("listening").innerHTML = ` document.getElementById("listening").innerHTML = `
<div class="now-playing-wrapper box"> <div class="now-playing-wrapper box">
<!--<h2>listening to:</h2>--> <!--<h2>listening to:</h2>-->
<a href="https://fm.yuki.k4w411.net/" target="_blank"> <a href="https://fm.yuki.k4w411.net/" target="_blank">
<div id="trackInfo"> <div id="trackInfo">
<img src="${json.track.image[1]["#text"]}"> <img src="${json.track.image[1]['#text']}">
<div id="trackInfoText"> <div id="trackInfoText">
<h3 id="trackName">${json.track.name}</h3> <h3 id="trackName">${json.track.name}</h3>
<p id="artistName">by ${json.track.artist["#text"]}</p> <p id="artistName">by ${json.track.artist['#text']}</p>
</div> </div>
</div> </div>
</a> </a>
</div> </div>
`; `
}; };
getTrack(); getTrack();
setInterval(() => { setInterval(() => { getTrack(); }, 10000);
getTrack();
}, 10000);