Improve tagging and toki pona support

This commit is contained in:
Bad Manners 2024-03-22 18:37:22 -03:00
parent d4a9dc9dbc
commit 18e98cdb3b
89 changed files with 971 additions and 956 deletions

View file

@ -20,14 +20,13 @@ const games = (await getCollection("games", (game) => !game.data.isDraft)).sort(
<a class="text-link hover:underline focus:underline" href={`/games/${game.slug}`}>
{game.data.thumbnail ? (
<Image
class="max-w-72"
class="max-w-[288px]"
src={game.data.thumbnail}
alt={`Thumbnail for ${game.data.title}`}
width={game.data.thumbnailWidth}
height={game.data.thumbnailHeight}
width={288}
/>
) : null}
<div class="max-w-72 text-sm">
<div class="max-w-[288px] text-sm">
<>
<span>{game.data.title}</span>
<br />

View file

@ -24,7 +24,7 @@ const totalPages = Math.ceil(page.total / page.size);
<GalleryLayout pageTitle="Stories">
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Stories</h1>
<p class="my-4">My main collection of content so far.</p>
<p class="my-4">The bulk of my content!</p>
<p class="text-center font-light text-stone-950 dark:text-white">
{
page.start == page.end
@ -68,19 +68,16 @@ const totalPages = Math.ceil(page.total / page.size);
<a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
{story.data.thumbnail ? (
<Image
class="w-48"
class="max-w-[192px]"
src={story.data.thumbnail}
alt={`Thumbnail for ${story.data.title}`}
width={story.data.thumbnailWidth}
height={story.data.thumbnailHeight}
width={192}
/>
) : null}
<div class="max-w-48 text-sm">
<>
<span>{story.data.title}</span>
<br />
<span class="italic">{formatDate(story.data.pubDate, "MMM d, yyyy", { locale: enUSLocale })}</span>
</>
<div class="max-w-[192px] text-sm">
<span>{story.data.title}</span>
<br />
<span class="italic">{formatDate(story.data.pubDate, "MMM d, yyyy", { locale: enUSLocale })}</span>
</div>
</a>
</li>

View file

@ -92,6 +92,12 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
return bskyMatch[1];
}
break;
case "itaku":
const itakuMatch = link.match(/^.*\bitaku\.ee\/profile\/([^\/]+)\/?$/);
if (itakuMatch && itakuMatch[1]) {
return itakuMatch[1];
}
break;
default:
throw new Error(`Unhandled website "${website}" in getUsernameForWebsite`);
}

View file

@ -3,13 +3,15 @@ import { getCollection } from "astro:content";
import { slug } from "github-slugger";
import GalleryLayout from "../layouts/GalleryLayout.astro";
const [stories, games] = await Promise.all([
getCollection("stories", (story) => !story.data.isDraft),
getCollection("games", (game) => !game.data.isDraft),
const [stories, games, tagCategories] = await Promise.all([
getCollection("stories"),
getCollection("games"),
getCollection("tag-categories"),
]);
const tagsSet = new Set<string>();
const draftOnlyTagsSet = new Set<string>();
const seriesList: Record<string, string> = {};
stories.forEach((story) => {
stories.filter((story) => !story.data.isDraft).forEach((story) => {
story.data.tags.forEach((tag) => {
tagsSet.add(tag);
});
@ -26,7 +28,7 @@ stories.forEach((story) => {
}
}
});
games.forEach((game) => {
games.filter((game) => !game.data.isDraft).forEach((game) => {
game.data.tags.forEach((tag) => {
tagsSet.add(tag);
});
@ -43,96 +45,53 @@ games.forEach((game) => {
}
}
});
const categorizedTags: Record<string, string[]> = {
"Types of vore": [
"oral vore",
"anal vore",
"cock vore",
"unbirth",
"tail vore",
"slit vore",
"sheath vore",
"nipple vore",
"chest maw vore",
],
"Body types": [
"anthro predator",
"feral predator",
"taur predator",
"ambiguous predator",
"human prey",
"anthro prey",
"feral prey",
"ambiguous prey",
],
Genders: [
"male predator",
"trans male predator",
"female predator",
"non-binary predator",
"ambiguous gender predator",
"male prey",
"female prey",
"trans female prey",
"non-binary prey",
"ambiguous gender prey",
],
"Relative size": ["macro predator", "micro prey", "size difference", "similar size", "same size", "smaller predator"],
Willingness: [
"willing predator",
"semi-willing predator",
"unwilling predator",
"asleep predator",
"willing prey",
"semi-willing prey",
"unwilling prey",
"asleep prey",
],
"Vore-related scenarios": [
"point of view",
"regurgitation",
"long-term endo",
"perma endo",
"implied perma endo",
"full tour",
"implied full tour",
"prey transfer",
"object vore",
"role reversal",
"nested vore",
"multiple prey",
"messy stomach",
"bladder vore",
"soul vore",
],
"Sexual content": ["nudity", "masturbation", "straight sex", "gay sex", "lesbian sex", "orgy"],
"Other kinks": [
"hyper",
"egg play",
"transformation",
"netorare",
"sizeplay",
"inflation",
"daddy play",
"BDSM",
"dubcon",
],
"Type of content": ["request", "commission", "flash fiction"],
"Recurring characters": ["Sam Brendan", "Beetle", "Muno"],
};
Object.entries(categorizedTags).forEach(([category, tagList]) => {
tagList.forEach((tag) => {
if (!tagsSet.delete(tag)) {
throw new Error(`Tag "${tag}" was added to category "${category}" but isn't present in any content`);
stories.filter((story) => story.data.isDraft).forEach((story) => {
story.data.tags.forEach((tag) => {
if (!tagsSet.has(tag)) {
draftOnlyTagsSet.add(tag);
}
});
});
games.filter((game) => game.data.isDraft).forEach((game) => {
game.data.tags.forEach((tag) => {
if (!tagsSet.has(tag)) {
draftOnlyTagsSet.add(tag);
}
});
});
const uncategorizedTagsSet = new Set(tagsSet);
if (tagsSet.size > 0) {
console.log("The following tags have no category:", [...tagsSet]);
categorizedTags["Uncategorized tags"] = [...tagsSet];
const categorizedTags: Array<[string, string, string[]]> = tagCategories.sort((a, b) => a.data.index - b.data.index).map(category => {
const tagList = category.data.tags.map(tag => {
if (typeof tag === "string") {
return tag
}
if (!("eng" in tag)) {
throw new Error(`"{[lang]: text}" tag must have an "eng" key: ${tag}`)
}
return tag["eng"]!
})
tagList.forEach((tag, index) => {
if (index !== tagList.findLastIndex((val) => tag == val)) {
throw new Error(`Duplicated tag "${tag}" found in multiple tag-categories`)
}
})
return [category.data.name, category.id, tagList.filter(tag => {
if (draftOnlyTagsSet.has(tag)) {
console.log(`Omitting draft-only tag "${tag}"`);
return false;
}
if (tagsSet.has(tag)) {
uncategorizedTagsSet.delete(tag);
}
return true;
})];
})
if (uncategorizedTagsSet.size > 0) {
const tagList = [...uncategorizedTagsSet];
console.log("The following tags have no category:", tagList);
categorizedTags.push(["Uncategorized tags", "others", tagList]);
}
---
@ -154,21 +113,23 @@ if (tagsSet.size > 0) {
</ul>
</section>
{
Object.entries(categorizedTags).map(([category, tagList]) => (
<section class="my-2" aria-labelledby={`category-${slug(category)}`}>
<h2 id={`category-${slug(category)}`} class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
{category}
</h2>
<ul class="flex max-w-3xl flex-wrap gap-x-2 gap-y-2 px-2">
{tagList.map((tag) => (
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white">
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
{tag}
</a>
</li>
))}
</ul>
</section>
))
categorizedTags.map(([category, categorySlug, tagList]) =>
tagList.length > 0 ? (
<section class="my-2" aria-labelledby={`category-${categorySlug}`}>
<h2 id={`category-${categorySlug}`} class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
{category}
</h2>
<ul class="flex max-w-3xl flex-wrap gap-x-2 gap-y-2 px-2">
{tagList.map((tag) => (
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white">
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
{tag}
</a>
</li>
))}
</ul>
</section>
) : null,
)
}
</GalleryLayout>