Add syndication links and start blog collection

This commit is contained in:
Bad Manners 2024-09-13 10:56:42 -03:00
parent 09934a9f8e
commit 9ff1986adc
Signed by: badmanners
GPG key ID: 8C88292CCB075609
80 changed files with 415 additions and 33 deletions
.vscode
src
components
content
data
i18n
layouts
pages
utils

View file

@ -32,6 +32,5 @@
},
"prettier.requireConfig": true,
"prettier.configPath": ".prettierrc.mjs",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}

View file

@ -57,8 +57,8 @@ import { IconTriangleExclamation } from "./icons";
<script>
const ageRestrictedModalSetup = () => {
const modal = document.querySelector<HTMLElementTagNameMap["div"]>("div#modal-age-restricted");
// Not an age-restricted page
if (!modal) {
// Not an age-restricted page
return;
}
if (modal !== document.querySelector("body>div#modal-age-restricted")) {

View file

@ -0,0 +1,100 @@
---
import type { Posts } from "../content/config";
import { t, type Lang } from "../i18n";
import { IconEkasPortal, IconFurAffinity, IconInkbunny, IconSoFurry, IconWeasyl } from "./icons/brands";
type Props = {
lang: Lang;
posts?: Posts;
};
const { lang, posts } = Astro.props;
const { eka, furaffinity, inkbunny, sofurry, weasyl } = posts ?? {};
const isVisible = eka || furaffinity || inkbunny || sofurry || weasyl;
---
{
isVisible ? (
<section id="external-posts-section" class="my-5 px-2 font-serif" aria-describedby="title-external-posts">
<>
<h2 id="title-external-posts" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
{t(lang, "published_content/syndication_see_also_on")}
</h2>
<ul id="external-posts" class="flex flex-wrap justify-around">
{eka ? (
<li class="min-w-12 grow">
<a
class="u-syndication text-link w-full"
href={eka.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_eka")}
>
<IconEkasPortal class="mx-auto" width="2rem" height="2rem" />
</a>
</li>
) : null}
{furaffinity ? (
<li class="min-w-12 grow">
<a
class="u-syndication text-link w-full"
href={furaffinity.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_furaffinity")}
>
<IconFurAffinity class="mx-auto" width="2rem" height="2rem" />
</a>
</li>
) : null}
{inkbunny ? (
<li class="min-w-12 grow">
<a
class="u-syndication text-link w-full"
href={inkbunny.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_inkbunny")}
>
<IconInkbunny class="mx-auto" width="2rem" height="2rem" />
</a>
</li>
) : null}
{sofurry ? (
<li class="min-w-12 grow">
<a
class="u-syndication text-link w-full"
href={sofurry.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_sofurry")}
>
<IconSoFurry class="mx-auto" width="2rem" height="2rem" />
</a>
</li>
) : null}
{weasyl ? (
<li class="min-w-12 grow">
<a
class="u-syndication text-link w-full"
href={weasyl.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_weasyl")}
>
<IconWeasyl class="mx-auto" width="2rem" height="2rem" />
</a>
</li>
) : null}
</ul>
</>
</section>
) : null
}
<script>
import tippy from "tippy.js";
import "tippy.js/dist/tippy.css";
const clipboardItems = document.querySelectorAll<HTMLElement>("ul#external-posts a[aria-label]");
tippy(clipboardItems, {
content: (el) => (el as HTMLElement).getAttribute("aria-label")!,
theme: "bm",
});
</script>

View file

@ -8,9 +8,10 @@ type Props = {
instance: string;
user: string;
postId: string;
blacklistedComments?: string[];
};
const { link, instance, user, postId } = Astro.props;
const { link, instance, user, postId, blacklistedComments } = Astro.props;
---
<section
@ -21,6 +22,7 @@ const { link, instance, user, postId } = Astro.props;
data-instance={instance}
data-user={user}
data-post-id={postId}
data-blacklisted={(blacklistedComments ?? []).join(",")}
>
<h2 id="title-comments-section" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
Comments
@ -45,7 +47,7 @@ const { link, instance, user, postId } = Astro.props;
class="group mx-auto w-64 rounded-lg bg-bm-300 px-4 py-1 text-stone-800 disabled:bg-bm-400 dark:bg-green-800 dark:text-stone-100 dark:disabled:bg-green-900"
id="load-comments-button"
>
<span class="block underline group-disabled:hidden">Click to load comments</span>
<span class="block hover:underline group-focus:underline group-disabled:hidden">Click to load comments</span>
<span class="hidden group-disabled:block">
<svg
style={{ width: "1.25rem", height: "1.25rem", display: "inline" }}
@ -144,6 +146,7 @@ const { link, instance, user, postId } = Astro.props;
reblogs_count: number;
created_at: string;
edited_at: string | null;
language: string | null;
account: {
username: string;
acct: string;
@ -162,7 +165,7 @@ const { link, instance, user, postId } = Astro.props;
descendants: Status[];
}
async function renderComments(section: Element, post: MastodonPost) {
async function renderComments(section: Element, post: MastodonPost, blacklistedComments: Set<string>) {
const commentsDescription = section.querySelector<HTMLElementTagNameMap["p"]>("p#comments-description")!;
const loadCommentsButton = section.querySelector<HTMLElementTagNameMap["button"]>("button#load-comments-button")!;
try {
@ -198,6 +201,13 @@ const { link, instance, user, postId } = Astro.props;
)!;
data.descendants.forEach((comment) => {
if (blacklistedComments.has(comment.id)) {
return;
}
if (blacklistedComments.has(comment.in_reply_to_id)) {
blacklistedComments.add(comment.id);
return;
}
const commentBox = commentTemplate.content.cloneNode(true) as DocumentFragment;
commentBox.firstElementChild!.id = `comment-${comment.id}`;
@ -303,6 +313,8 @@ const { link, instance, user, postId } = Astro.props;
if (!post.link || !post.instance || !post.user || !post.postId) {
return;
}
const blacklisted = commentSection.dataset.blacklisted;
const blacklistedComments = new Set(blacklisted ? blacklisted.split(",") : undefined);
const loadCommentsButton =
commentSection.querySelector<HTMLElementTagNameMap["button"]>("button#load-comments-button")!;
loadCommentsButton.addEventListener(
@ -310,7 +322,7 @@ const { link, instance, user, postId } = Astro.props;
(e) => {
e.preventDefault();
loadCommentsButton.disabled = true;
renderComments(commentSection, post as MastodonPost);
renderComments(commentSection, post as MastodonPost, blacklistedComments);
},
{ once: true },
);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,15 @@
---
import SVGIcon from "../SVGIcon.astro";
type Props = {
width: string;
height: string;
class?: string;
};
---
<SVGIcon {...Astro.props} viewBox="0 0 72 72">
<path
d="M58.914 44.015c-.247.468-1.187 1.528-1.95 1.98-1.493.873-1.984 1.014-1.65.48.299-.483-1.403-.035-2.613.696-.55.326-1.211.898-1.326.783-.388-.388-1.69 3.786-1.535 4.82.174 2.417-1.846 3.545-3.733 5.455-1.885 1.908-3.682 3.793-3.816 3.793-.134 0-.756-.771-2.183-2.496-3.132-3.781-4.04-5.785-4.088-8.04.07-.472 1.116-.812 1.943-1.045 1.046-.29 2.79-1.198 2.79-1.198s2.917-1.63 3.026-1.694c.684-.35 1.374-.929 1.755-1.268 1.093-.943 1.736-2.4 1.929-3.16.134-.536-.288-1.356-.756-2.11l-.66-1.14.813-3.384c.467-1.795.455-4.315 1.54-5.147 2.835-.935 5.735-5.197 8.36-9.084 4.032-5.97 5.191-10.89 3.14-13.33-1.568-1.86-3.662-1.506-10.12 1.713-2.213 1.104-5.978 3.183-8.363 4.618-2.385 1.438-4.386 2.57-4.437 2.519-.05-.055.13-1.459.401-3.121.755-4.594.97-9.542.503-11.495C37.381 1.068 36.273-.016 34.83 0c-.864.008-1.846.413-2.893 1.234-4.306 3.384-9.427 10.025-11.403 16.29-1.62 5.134-3.793 16.975-3.538 19.958.52 6.127.924 5.226-.33 7.368C13.137 50.87 11 59.585 11 67.936c0 3.744-.43 4.064.125 4.064h2.986c.444-.083.287-.795.444-5.393.196-5.765.79-9.322 2.342-14.018.849-2.57 3.149-7.487 4.092-8.737.16-.216.44-.381.46-.577.035-.32-.134-.74-.516-1.534-.57-1.182-.837-2.047-.837-5.317.173-3.352.232-3.984.845-5.364.126-.291.524-1.019.68-1.56.33-1.125.311-2.488.546-3.863 1.584-9.31 3.232-14.91 8.526-20.452 3.168-2.848 4.552-3.734 4.54 1.023.334 4-1.334 12.948-3.056 17.381-.519 1.337-1.179 2.638-1.123 2.693.255.326 3.547-.484 3.91-1.597.133-.405.174-.398.318-.376.143.021 1.478 1.026 1.921 1.131.495.11 1.828-.515 1.876-.715.046-.2-2.593-1.83-2.774-1.997-.034-.388-.099-.453-.015-.825.084-.372.14-.615.899-1.105.758-.49 16.982-10.218 19.577-10.27.94-.039 1.35 1.426 1.368 1.935.017.509.04 2.72-.999 4.24-1.038 1.521-8.414 11.11-9.282 11.873-.867.763-2.678.75-3.319.386s-3.705-1.568-3.437-1.069c.271.503 1.255 1.324 1.79 1.933.369.44.99 1.265 1.38 1.835l.585.998-1.65 7.365.758 1.113c.597 1.014.605 1.143.126 2.142-.236.479-1.026 1.245-1.706 1.93-.31.286-4.578 3.006-6.96 3.776-1.781.515-1.615.295-3.12-.008-1.506-.302-4.52-2.453-5.124-1.858-.604.596.877 1.813 1.328 1.988.45.174.61.564 1.328.859.501.216 1.186 1.002 1.221 1.486.031.633.055.525.094 1.126.095 3.914.833 5.966 3.942 12.862 1.207 2.677 2.29 5.247 2.41 5.715.14.562.11.845.542.845h1.963c.597 0-.059-1.753-2.244-6.626-3.474-7.758-4.613-11.628-2.877-9.005.62 1.03 2.13 3.055 3.352 4.493 4.453 5.247 5.384 7.306 4.39 9.688-.42 1.002-.594 1.45-.26 1.45h2.09c.448 0 .579-.137.898-.896.573-1.378 1.536-2.204.95-3.842l-.243-1.14 2.936-4.048c1.064-1.399 2.203-3.179 2.738-3.761.538-.582.664-.797.664-.797.495.24 2.253-.09 2.522-.351.268-.261-.632-.143-.749-.538-.102-.169.707-.677 1.422-.965 1.418-.57 2.842-2.264 2.932-2.975.245-.297.267.551.277.93-.024.628-1.583 2.249-2.043 3.22-.46.973-.692 1.46-.543 2.44.149.98 1.215 1.857 2.648 1.787 1.432-.07 2.275-.936 2.295-2.316.02-1.565-1.778-6.887-1.667-6.864.11.028.382-7.094.22-7.793-.162-.699-.437-.795-.437-.795zm-33.575-7.213c.008.425 1.333.585 1.44-.075.157-.99 1.24-1.77 2.004-1.707.764.061 1.194.103 1.556.815.363.713.14 1.378.24 1.654.102.276 1.16.317 1.227-.133.066-.451.319-2.732-.927-3.389-1.247-.657-2.26-.776-3.545-.294-1.286.482-2.019 1.863-1.995 3.13zm15.64 1.859c.066-.21.446-.797.856-.574.41.222.252.614.245.81-.007.195.68.652.798.415.118-.237.186-.63.191-.795.006-.166.081-.753-.226-1.053-.307-.3-.894-.548-1.583-.33-.69.217-1.111.871-1.195 1.15-.084.277.846.587.913.377zm-6.251-1.067s.998 1.791 1.358 2.158c.36.367 1.502.72 2.08.456.578-.263 1.245-1.528 1.276-1.775a136.192 136.192 0 0 1-4.714-.84zm-.063 5.955c-.837.182-2.516.256-3.887-.012-1.371-.271-2.343.357-2.013.676.668.637 1.955.795 2.753.906 1.268.173 2.766-.017 3.207-.085.44-.068 1.364-.557 1.482-.47.06.044.616.507 1.245.822.596.298 1.47.464 1.727.503.256.039.982-.631.795-.747-.188-.115-.727-.27-1.018-.367a4.407 4.407 0 0 1-1.13-.542c-.533-.351-1.057-.865-1.2-1.23-.144-.366-.389.024-.7.157-.412.177-.424.205-1.261.39zm15.387 14.486c-1.143 1.549-1.374 1.888-2.364 3.201-1.446 1.91-2.78 3.526-2.955 3.585-.177.058-.511-.19-.735-.55-.374-.602-.146-.956 2.542-3.946 1.154-1.29 1.563-1.734 2.73-3.012.252-.272.458-.51.59-.503.132.006.631.368.732.44.1.072-.145.329-.54.785zM20.637 68.697c-.146 2.28-.126 3.301.118 3.301h1.764c.181 0 .123-.427.13-.887.031-3.03.812-8.371 1.31-12.02.116-.793-.597-1.307-1.142-1.115-.543.192-1.968 7.365-2.18 10.721z"
></path>
</SVGIcon>

View file

@ -0,0 +1,15 @@
---
import SVGIcon from "../SVGIcon.astro";
type Props = {
width: string;
height: string;
class?: string;
};
---
<SVGIcon {...Astro.props} viewBox="0 0 64 64">
<path
d="M16.998 56.139c-3.483-.313-6.077-1.151-8.817-2.85-1.2-.745-1.99-1.373-3.22-2.565-1.27-1.23-2.101-2.23-3.042-3.663C1.323 46.154.004 43.696 0 43.484c-.003-.127.642-.091 1.265.07.316.082.59.134.609.116.019-.019-.066-.356-.187-.75-.703-2.275-.741-5.302-.1-7.857.679-2.702 2.023-5.117 3.99-7.169l1-1.043.178.567c.098.311.18.697.184.856.004.159.034.29.067.29s.41-.214.839-.474c3.491-2.12 7.817-2.179 11.225-.154.818.486 2.199 1.744 2.774 2.528.545.742 1.16 2.037 1.461 3.08.431 1.489.534 3.07.317 4.867l-.052.428-.707-.185c-1.395-.363-3.021-.203-4.173.411-.818.437-1.795 1.463-2.194 2.306-.32.674-.34.79-.34 1.876 0 1.085.021 1.203.337 1.87.415.876 1.157 1.613 2.083 2.069.722.356 1.837.636 2.527.636 1.224 0 3.052-.707 4.125-1.596 1.722-1.427 2.622-3.392 2.738-5.979.126-2.809-.505-5.298-2.39-9.42-1.822-3.984-2.493-6.404-2.599-9.38-.047-1.327-.022-1.926.118-2.764.42-2.509 1.414-4.424 3.18-6.127 2.175-2.097 5.092-3.31 8.256-3.437 1.262-.05 2.372.037 3.056.241.138.041.18-.07.23-.611.094-1.003.13-1.059.533-.851 1.488.766 3.272 2.398 4.234 3.875 1.201 1.842 1.712 3.483 1.802 5.79.065 1.662-.025 2.381-.92 7.387-.766 4.282-.875 5.151-.878 7.034-.003 1.482.024 1.76.228 2.31.198.533.775 1.323.965 1.321.032 0 .246-.367.476-.816.76-1.484 2.01-3.227 3.211-4.478 1.323-1.376 2.578-2.318 4.01-3.01l1.006-.486-.208-.473c-.146-.332-.223-.795-.258-1.56l-.05-1.086.41.07c1.318.222 2.772.758 3.846 1.42.282.173.524.315.538.315.013 0 .033-.344.045-.764.01-.42.06-.873.11-1.006l.09-.243.646.432c.847.565 2.001 1.717 2.788 2.78.347.47.661.865.698.88.038.014.2-.227.361-.535.161-.308.43-.722.597-.92l.305-.361.321.702c1.306 2.858 1.648 8.456.85 13.93-.844 5.791-2.563 9.862-5.357 12.686-2.172 2.195-4.194 3.067-7.104 3.063-1.668-.002-2.857-.2-4.497-.747-1.439-.48-2.255-.873-3.407-1.639-.922-.612-2.01-1.65-1.907-1.818.087-.14.98-.44 1.32-.443.53-.003.57-.121.27-.797-1.435-3.23-1.511-7.903-.197-12.062.147-.466.228-.882.18-.925-.048-.042-.29-.155-.537-.25-1.532-.593-2.635-1.942-3.154-3.858-.153-.565-.185-1.116-.181-3.158.004-2.199.044-2.772.362-5.158.468-3.513.507-5.76.121-6.88-.694-2.01-2.422-3.286-4.155-3.069-1.851.232-3.602 2.117-3.965 4.27-.295 1.75.205 3.449 2.138 7.258 1.965 3.873 2.696 5.787 3.218 8.42.623 3.15.589 6.597-.091 9.26-1.628 6.378-6.329 10.363-13.474 11.424-1.344.2-4.718.285-6.158.156zm38.56-5.629c1.611-.75 3.426-3.418 4.488-6.598 1.291-3.866 1.285-8.376-.015-11.01-.432-.875-1.105-1.543-1.66-1.647-.81-.152-2.248.65-3.448 1.922-1.708 1.81-2.954 4.326-3.559 7.182-.318 1.505-.4 4.178-.17 5.572.39 2.366 1.245 3.993 2.402 4.57.69.343 1.238.346 1.961.01zm-42.375-7.5c.099-1.947.728-3.439 2.058-4.878 1.197-1.295 2.751-2.147 4.532-2.484.538-.101.962-.234.962-.3 0-.066-.094-.485-.208-.931-.487-1.904-1.661-3.363-3.396-4.22-1.314-.648-2.2-.823-3.87-.764-1.518.054-2.535.329-3.684.995-.964.56-2.384 1.997-2.976 3.014-.489.838-1.332 3.164-1.196 3.3.106.105 1.495-.15 1.873-.345.175-.091.342-.142.37-.114.028.028.155.692.282 1.475.127.783.253 1.446.281 1.474.028.027.642-.086 1.366-.252.724-.167 1.39-.305 1.479-.307.138-.004.156.142.119.988-.076 1.746.384 3.107 1.37 4.056.244.233.474.425.512.425.038 0 .095-.51.126-1.132z"
></path>
</SVGIcon>

View file

@ -0,0 +1,15 @@
---
import SVGIcon from "../SVGIcon.astro";
type Props = {
width: string;
height: string;
class?: string;
};
---
<SVGIcon {...Astro.props} viewBox="0 0 24 24">
<path
d="M21.23 4.156a8.488 8.488 0 0 0-5.871-1.857c-3.766.243-6.324 2.662-7.364 6.481-1.28-1.224-1.892-3.238-2.093-5.54-1.02.215-1.658.702-2.233 1.237.445 2.316 1.802 4.015 3.264 5.158-2.559.317-5.99 2.442-6.771 4.904-.507 1.598.258 3.415 1.283 4.52 1.237 1.333 3.75 1.998 6.355 1.754.037.362-.104.536-.058.907 4.067-.306 7.174-1.646 10.04-3.894 1.119-.877 2.659-2.037 3.756-3.227 1.101-1.192 2.296-2.578 2.443-4.52.21-2.79-1.236-4.694-2.751-5.923zm-1.434 10.938c-1.035 1.001-2.241 1.797-3.351 2.675-1.249-1.987-2.583-3.984-3.887-5.917.017 2.63.006 5.432.04 7.957-.78.381-1.789.558-2.744.763-1.935-2.917-3.968-5.99-5.961-8.908.693-.447 1.627-.785 2.478-1.075 1.419 2.05 2.729 4.253 4.171 6.333.019-3.113-.009-6.673-.061-9.919a14.175 14.175 0 0 0 1.527-.434c1.813 2.721 3.553 5.628 5.464 8.359a547.35 547.35 0 0 1-.018-9.768c.858-.282 1.803-.535 2.669-.809.02 3.499-.338 7.128-.327 10.743z"
></path>
</SVGIcon>

View file

@ -0,0 +1,5 @@
export { default as IconEkasPortal } from "./IconEkasPortal.astro";
export { default as IconFurAffinity } from "./IconFurAffinity.astro";
export { default as IconInkbunny } from "./IconInkbunny.astro";
export { default as IconSoFurry } from "./IconSoFurry.astro";
export { default as IconWeasyl } from "./IconWeasyl.astro";

2
src/content/blog/drafts/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -78,6 +78,14 @@ const websiteLinks = z.object({
return { link, username };
}),
),
sofurrybeta: z.object({ link: z.string().url(), username: z.string() }).or(
z.string().transform((link, ctx) => {
const { username } = parseRegex<{ username: string }>(
/^(?:https?:\/\/)?sofurrybeta.com\/u\/(?<username>[^\/]+)\/?$/,
)(link, ctx);
return { link, username };
}),
),
mastodon: z
.object({ link: z.string().url(), username: z.string().regex(/^[^@]+@[^@]+$/) })
.or(
@ -144,7 +152,6 @@ const publishedContent = z.object({
// Required parameters
title: z.string(),
authors: userList,
contentWarning: z.string().trim(),
// Required parameters, but optional for drafts (isDraft === true)
pubDate: z
.date()
@ -154,10 +161,10 @@ const publishedContent = z.object({
tags: z.array(z.string()),
// Optional parameters
isDraft: z.boolean().default(false),
isAgeRestricted: z.boolean().default(true),
relatedStories: z.array(reference("stories")).default([]),
relatedGames: z.array(reference("games")).default([]),
lang: lang,
copyrightedCharacters: copyrightedCharacters,
series: reference("series").optional(),
posts: z
.object({
@ -191,6 +198,18 @@ const publishedContent = z.object({
)(link, ctx);
return { link, postId };
}),
sofurrybeta: z.string().transform((link, ctx) => {
const { postId } = parseRegex<{ postId: string }>(
/^(?:https?:\/\/)sofurrybeta\.com\/s\/(?<postId>[a-zA-Z0-9]+)\/?$/,
)(link, ctx);
return { link, postId };
}),
itch: z.string().transform((link, ctx) => {
const { user, postId } = parseRegex<{ user: string; postId: string }>(
/^(?:https?:\/\/)(?<user>[a-z-]+)\.itch\.io\/(?<postId>[a-z0-9_-]+)\/?$/,
)(link, ctx);
return { link, user, postId };
}),
twitter: z.string().transform((link, ctx) => {
const { user, postId } = parseRegex<{ user: string; postId: string }>(
/^(?:https?:\/\/)(?:www\.)?(?:twitter\.com|x\.com)\/(?<user>[a-zA-Z0-9_-]+)\/status\/(?<postId>[1-9]\d*)\/?$/,
@ -212,6 +231,7 @@ const publishedContent = z.object({
})
.partial()
.default({}),
blacklistedMastodonComments: z.array(z.string()).default([]),
});
// Types
@ -232,17 +252,19 @@ const storiesCollection = defineCollection({
z
.object({
// Required parameters, but optional for drafts (isDraft === true)
contentWarning: z.string().trim(),
wordCount: z.number().int().optional(),
thumbnail: image().optional(),
// Optional parameters
shortTitle: z.string().optional(),
commissioners: userList.optional(),
requesters: userList.optional(),
summary: z.string().trim().optional(),
thumbnailWidth: z.number().int().optional(),
thumbnailHeight: z.number().int().optional(),
prev: reference("stories").nullish(),
next: reference("stories").nullish(),
copyrightedCharacters: copyrightedCharacters,
shortTitle: z.string().optional(),
commissioners: userList.optional(),
requesters: userList.optional(),
summary: z.string().trim().optional(),
})
.and(publishedContent)
.refine(({ isDraft, description }) => isDraft || description, `Missing "description" for published story`)
@ -253,7 +275,11 @@ const storiesCollection = defineCollection({
.refine(({ isDraft, wordCount }) => isDraft || wordCount, `Missing "wordCount" for published story`)
.refine(({ isDraft, pubDate }) => isDraft || pubDate, `Missing "pubDate" for published story`)
.refine(({ isDraft, thumbnail }) => isDraft || thumbnail, `Missing "thumbnail" for published story`)
.refine(({ isDraft, tags }) => isDraft || tags.length, `Missing "tags" for published story`),
.refine(({ isDraft, tags }) => isDraft || tags.length, `Missing "tags" for published story`)
.refine(
({ posts, blacklistedMastodonComments }) => !blacklistedMastodonComments.length || posts.mastodon,
`Cannot include "blacklistedMastodonComments" without a Mastodon post`,
),
});
const gamesCollection = defineCollection({
@ -262,6 +288,7 @@ const gamesCollection = defineCollection({
z
.object({
// Required parameters, but optional for drafts (isDraft === true)
contentWarning: z.string().trim(),
platforms: z.array(platform).default([]),
thumbnail: image().optional(),
// Optional parameters
@ -269,6 +296,7 @@ const gamesCollection = defineCollection({
thumbnailHeight: z.number().int().optional(),
prev: reference("games").nullish(),
next: reference("games").nullish(),
copyrightedCharacters: copyrightedCharacters,
})
.and(publishedContent)
.refine(({ isDraft, description }) => isDraft || description, `Missing "description" for published game`)
@ -276,7 +304,36 @@ const gamesCollection = defineCollection({
.refine(({ isDraft, platforms }) => isDraft || platforms.length, `Missing "platforms" for published game`)
.refine(({ isDraft, pubDate }) => isDraft || pubDate, `Missing "pubDate" for published game`)
.refine(({ isDraft, thumbnail }) => isDraft || thumbnail, `Missing "thumbnail" for published game`)
.refine(({ isDraft, tags }) => isDraft || tags.length, `Missing "tags" for published game`),
.refine(({ isDraft, tags }) => isDraft || tags.length, `Missing "tags" for published game`)
.refine(
({ posts, blacklistedMastodonComments }) => !blacklistedMastodonComments.length || posts.mastodon,
`Cannot include "blacklistedMastodonComments" without a Mastodon post`,
),
});
const blogCollection = defineCollection({
type: "content",
schema: ({ image }) =>
z
.object({
// Required parameters, but optional for drafts (isDraft === true)
thumbnail: image().optional(),
// Optional parameters
summary: z.string().trim().optional(),
thumbnailWidth: z.number().int().optional(),
thumbnailHeight: z.number().int().optional(),
prev: reference("blog").nullish(),
next: reference("blog").nullish(),
})
.and(publishedContent)
.refine(({ isDraft, description }) => isDraft || description, `Missing "description" for published story`)
.refine(({ isDraft, pubDate }) => isDraft || pubDate, `Missing "pubDate" for published story`)
.refine(({ isDraft, thumbnail }) => isDraft || thumbnail, `Missing "thumbnail" for published story`)
.refine(({ isDraft, tags }) => isDraft || tags.length, `Missing "tags" for published story`)
.refine(
({ posts, blacklistedMastodonComments }) => !blacklistedMastodonComments.length || posts.mastodon,
`Cannot include "blacklistedMastodonComments" without a Mastodon post`,
),
});
// Data collections
@ -332,6 +389,7 @@ const tagCategoriesCollection = defineCollection({
export const collections = {
stories: storiesCollection,
games: gamesCollection,
blog: blogCollection,
users: usersCollection,
series: seriesCollection,
"tag-categories": tagCategoriesCollection,

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245838/accommodation-vore-story
inkbunny: https://inkbunny.net/s/3009993
sofurry: https://www.sofurry.com/view/1992347
sofurrybeta: https://sofurrybeta.com/s/dmBBW6Zm
tags:
- anal vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245835/c-addictive-additions-vore-story
inkbunny: https://inkbunny.net/s/3009992
sofurry: https://www.sofurry.com/view/1992344
sofurrybeta: https://sofurrybeta.com/s/ZmM3E4q1
tags:
- oral vore
- anal vore
@ -30,6 +31,7 @@ tags:
- semi-willing prey
- similar size
- implied perma endo
- endo trait theft
- straight sex
- gay sex
- hyper

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245812/annivoresary-vore-story
inkbunny: https://inkbunny.net/s/3009934
sofurry: https://www.sofurry.com/view/1992300
sofurrybeta: https://sofurrybeta.com/s/3egLylPm
tags:
- oral vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245853/c-better-in-bully-batter-vore-story
inkbunny: https://inkbunny.net/s/3010009
sofurry: https://www.sofurry.com/view/1992371
sofurrybeta: https://sofurrybeta.com/s/dmW3GPwm
tags:
- cock vore
- anthro predator
@ -27,6 +28,7 @@ tags:
- unwilling prey
- similar size
- implied perma endo
- endo trait theft
- straight sex
- gay sex
- orgy

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2326739/big-haul-vore-story
inkbunny: https://inkbunny.net/s/3179339
sofurry: https://www.sofurry.com/view/2074207
sofurrybeta: https://sofurrybeta.com/s/GnDLVoGn
tags:
- unbirth
- anthro predator

View file

@ -18,6 +18,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245859/birdroom-vore-story
inkbunny: https://inkbunny.net/s/3010032
sofurry: https://www.sofurry.com/view/1992379
sofurrybeta: https://sofurrybeta.com/s/7ejYXvL1
tags:
- Sam Brendan
- Beetle

View file

@ -18,6 +18,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2322878/bladder-filler-vore-story
inkbunny: https://inkbunny.net/s/3168729
sofurry: https://www.sofurry.com/view/2069991
sofurrybeta: https://sofurrybeta.com/s/J1O5Z3zm
tags:
- Beetle
- cock vore

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2308019/bottom-of-the-food-chain-vore-story
inkbunny: https://inkbunny.net/s/3130702
sofurry: https://www.sofurry.com/view/2054568
sofurrybeta: https://sofurrybeta.com/s/Vm2b34Gm
tags:
- Muno
- oral vore

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245850/butting-into-their-plans-vore-story
inkbunny: https://inkbunny.net/s/3010005
sofurry: https://www.sofurry.com/view/1992365
sofurrybeta: https://sofurrybeta.com/s/znJE5M3m
tags:
- anal vore
- feral predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245840/delicacy-s-dare-vore-story
inkbunny: https://inkbunny.net/s/3009994
sofurry: https://www.sofurry.com/view/1992348
sofurrybeta: https://sofurrybeta.com/s/V1YARGy1
tags:
- oral vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245808/eggs-for-months-vore-story
inkbunny: https://inkbunny.net/s/3009928
sofurry: https://www.sofurry.com/view/1992262
sofurrybeta: https://sofurrybeta.com/s/lmXB9VG1
tags:
- sheath vore
- feral predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2291372/engaging-contacts-vore-story
inkbunny: https://inkbunny.net/s/3088451
sofurry: https://www.sofurry.com/view/2035092
sofurrybeta: https://sofurrybeta.com/s/YnLrbBpe
tags:
- oral vore
- unbirth

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245855/flavorful-favor-vore-story
inkbunny: https://inkbunny.net/s/3010014
sofurry: https://www.sofurry.com/view/1992375
sofurrybeta: https://sofurrybeta.com/s/WelWOaj1
tags:
- Beetle
- oral vore

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245830/for-the-night-vore-story
inkbunny: https://inkbunny.net/s/3009989
sofurry: https://www.sofurry.com/view/1992339
sofurrybeta: https://sofurrybeta.com/s/P1EMLv81
tags:
- anal vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2320112/gentle-and-cruel-vore-story
inkbunny: https://inkbunny.net/s/3161450
sofurry: https://www.sofurry.com/view/2066780
sofurrybeta: https://sofurrybeta.com/s/3egLyEdm
tags:
- oral vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245852/hate-to-sea-it-vore-story
inkbunny: https://inkbunny.net/s/3010007
sofurry: https://www.sofurry.com/view/1992369
sofurrybeta: https://sofurrybeta.com/s/pnGE786e
tags:
- unbirth
- feral predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245845/hungry-for-love-vore-story
inkbunny: https://inkbunny.net/s/3010001
sofurry: https://www.sofurry.com/view/1992358
sofurrybeta: https://sofurrybeta.com/s/pnGE787e
tags:
- oral vore
- feral predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245818/hyper-hunger-vore-story
inkbunny: https://inkbunny.net/s/3009945
sofurry: https://www.sofurry.com/view/1992317
sofurrybeta: https://sofurrybeta.com/s/JewQ7gYn
tags:
- oral vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245823/insistence-and-assistance-vore-story
inkbunny: https://inkbunny.net/s/3009949
sofurry: https://www.sofurry.com/view/1992324
sofurrybeta: https://sofurrybeta.com/s/A1pxJED1
tags:
- oral vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245829/lactation-action-vore-story
inkbunny: https://inkbunny.net/s/3009986
sofurry: https://www.sofurry.com/view/1992335
sofurrybeta: https://sofurrybeta.com/s/0mvw0ZQn
tags:
- nipple vore
- oral vore

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245828/latest-catch-vore-story
inkbunny: https://inkbunny.net/s/3009984
sofurry: https://www.sofurry.com/view/1992331
sofurrybeta: https://sofurrybeta.com/s/rn6Jyzpn
tags:
- cock vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245821/never-too-late-vore-story
inkbunny: https://inkbunny.net/s/3009947
sofurry: https://www.sofurry.com/view/1992320
sofurrybeta: https://sofurrybeta.com/s/5nz8gOle
tags:
- cock vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2305403/noble-fire-vore-story
inkbunny: https://inkbunny.net/s/3124162
sofurry: https://www.sofurry.com/view/2051777
sofurrybeta: https://sofurrybeta.com/s/lmXB9qy1
tags:
- oral vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2291367/r-overzealous-zenko-vore-story
inkbunny: https://inkbunny.net/s/3088442
sofurry: https://www.sofurry.com/view/2035087
sofurrybeta: https://sofurrybeta.com/s/Xek5wP5n
tags:
- chest maw vore
- taur predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2257653/part-of-the-show-vore-story
inkbunny: https://inkbunny.net/s/3030429
sofurry: https://www.sofurry.com/view/2001881
sofurrybeta: https://sofurrybeta.com/s/r17DQXBe
tags:
- Sam Brendan
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245810/pet-sit-saturday-vore-story
inkbunny: https://inkbunny.net/s/3009931
sofurry: https://www.sofurry.com/view/1992266
sofurrybeta: https://sofurrybeta.com/s/qmywZNW1
tags:
- anal vore
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
furaffinity: https://www.furaffinity.net/view/57675502/
inkbunny: https://inkbunny.net/s/3391838
sofurry: https://www.sofurry.com/view/2161474
sofurrybeta: https://sofurrybeta.com/s/dmBBkvXm
weasyl: https://www.weasyl.com/~badmanners/submissions/2401625/playing-it-safe
twitter: https://x.com/BadManners__/status/1821526198150680589
bluesky: https://bsky.app/profile/badmanners.xyz/post/3kz7gvvwyvt2c

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245847/reaching-for-the-full-moon-vore-story
inkbunny: https://inkbunny.net/s/3010003
sofurry: https://www.sofurry.com/view/1992360
sofurrybeta: https://sofurrybeta.com/s/V1YARWD1
tags:
- oral vore
- anthro predator

View file

@ -15,6 +15,7 @@ posts:
furaffinity: https://www.furaffinity.net/view/57459926/
inkbunny: https://inkbunny.net/s/3377727
sofurry: https://www.sofurry.com/view/2156241
sofurrybeta: https://sofurrybeta.com/s/ZmM3QrW1
weasyl: https://www.weasyl.com/~badmanners/submissions/2396279/c-rose-s-binge
mastodon: https://meow.social/@BadManners/112827108738415986
tags:

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245848/ruffling-some-feathers-vore-story
inkbunny: https://inkbunny.net/s/3010004
sofurry: https://www.sofurry.com/view/1992363
sofurrybeta: https://sofurrybeta.com/s/k1VYoGDn
tags:
- Muno
- oral vore

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245831/spontaneous-sleepover-vore-story
inkbunny: https://inkbunny.net/s/3009991
sofurry: https://www.sofurry.com/view/1992340
sofurrybeta: https://sofurrybeta.com/s/rm8DNdqm
tags:
- tail vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2345545/taken-in
inkbunny: https://inkbunny.net/s/3231516
sofurry: https://www.sofurry.com/view/2096828
sofurrybeta: https://sofurrybeta.com/s/B1QQYaz1
tags:
- Sam Brendan
- feral predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245857/tasting-high-consequences-vore-story
inkbunny: https://inkbunny.net/s/3010016
sofurry: https://www.sofurry.com/view/1992377
sofurrybeta: https://sofurrybeta.com/s/Oe5RNpMm
tags:
- oral vore
- feral predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2341116/c-team-building
inkbunny: https://inkbunny.net/s/3219382
sofurry: https://www.sofurry.com/view/2091230
sofurrybeta: https://sofurrybeta.com/s/RnoXYox1
tags:
- cock vore
- anal vore

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2291370/r-team-effort-vore-story
inkbunny: https://inkbunny.net/s/3088444
sofurry: https://www.sofurry.com/view/2035089
sofurrybeta: https://sofurrybeta.com/s/ynZ7L8w1
tags:
- cock vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245824/the-last-livestream-vore-story
inkbunny: https://inkbunny.net/s/3009975
sofurry: https://www.sofurry.com/view/1992325
sofurrybeta: https://sofurrybeta.com/s/B1QQYK71
tags:
- unbirth
- anthro predator

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245842/the-lost-of-the-marshes-bonus-quince-s-fantasy
inkbunny: https://inkbunny.net/s/3009997
sofurry: https://www.sofurry.com/view/1992350
sofurrybeta: https://sofurrybeta.com/s/k1VYoJGn
tags:
- oral vore
- anthro predator

View file

@ -15,6 +15,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245800/the-lost-of-the-marshes-chapter-1-found
inkbunny: https://inkbunny.net/s/3009918
sofurry: https://www.sofurry.com/view/1992256
sofurrybeta: https://sofurrybeta.com/s/Rm0K4OKe
tags:
- oral vore
- feral predator

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245861/the-lost-of-the-marshes-chapter-10-memory
inkbunny: https://inkbunny.net/s/3010039
sofurry: https://www.sofurry.com/view/1992381
sofurrybeta: https://sofurrybeta.com/s/ve3QaApn
tags:
- oral vore
- anal vore

View file

@ -15,6 +15,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2325367/the-lost-of-the-marshes-chapter-11-familiar
inkbunny: https://inkbunny.net/s/3175040
sofurry: https://www.sofurry.com/view/2072674
sofurrybeta: https://sofurrybeta.com/s/RnoXYo01
tags:
- oral vore
- cock vore

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245802/the-lost-of-the-marshes-chapter-2-trust
inkbunny: https://inkbunny.net/s/3009921
sofurry: https://www.sofurry.com/view/1992257
sofurrybeta: https://sofurrybeta.com/s/dePdgr5e
tags:
- oral vore
- feral predator

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245804/the-lost-of-the-marshes-chapter-3-home
inkbunny: https://inkbunny.net/s/3009926
sofurry: https://www.sofurry.com/view/1992259
sofurrybeta: https://sofurrybeta.com/s/q1xJz8Zm
tags:
- oral vore
- macro predator

View file

@ -19,6 +19,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245805/the-lost-of-the-marshes-chapter-4-change
inkbunny: https://inkbunny.net/s/3009927
sofurry: https://www.sofurry.com/view/1992260
sofurrybeta: https://sofurrybeta.com/s/ynd4LA7n
tags:
- oral vore
- anthro predator

View file

@ -19,6 +19,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245811/the-lost-of-the-marshes-chapter-5-intersection
inkbunny: https://inkbunny.net/s/3009933
sofurry: https://www.sofurry.com/view/1992297
sofurrybeta: https://sofurrybeta.com/s/zeRb9Nge
tags:
- oral vore
- anthro predator

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245813/the-lost-of-the-marshes-chapter-6-pleasure
inkbunny: https://inkbunny.net/s/3009938
sofurry: https://www.sofurry.com/view/1992302
sofurrybeta: https://sofurrybeta.com/s/J1O5Zqgm
tags:
- oral vore
- unbirth

View file

@ -17,6 +17,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245817/the-lost-of-the-marshes-chapter-7-honesty
inkbunny: https://inkbunny.net/s/3009943
sofurry: https://www.sofurry.com/view/1992307
sofurrybeta: https://sofurrybeta.com/s/GnDLVbjn
tags:
- oral vore
- anal vore

View file

@ -15,6 +15,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245827/the-lost-of-the-marshes-chapter-8-estranged
inkbunny: https://inkbunny.net/s/3009983
sofurry: https://www.sofurry.com/view/1992326
sofurrybeta: https://sofurrybeta.com/s/rn92NpQ1
tags:
- oral vore
- anal vore

View file

@ -15,6 +15,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245844/the-lost-of-the-marshes-chapter-9-stuck
inkbunny: https://inkbunny.net/s/3010000
sofurry: https://www.sofurry.com/view/1992354
sofurrybeta: https://sofurrybeta.com/s/znJE5M7m
tags:
- oral vore
- slit vore

View file

@ -15,6 +15,7 @@ posts:
furaffinity: https://www.furaffinity.net/view/56026627/
inkbunny: https://inkbunny.net/s/3283508
sofurry: https://www.sofurry.com/view/2118138
sofurrybeta: https://sofurrybeta.com/s/0mvwLB0n
weasyl: https://www.weasyl.com/~badmanners/submissions/2363560/tiny-accident
bluesky: https://bsky.app/profile/badmanners.xyz/post/3kok52wijz32c
mastodon: https://meow.social/@BadManners/112157812554023271

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245854/tomo-moku-lipu-pi-moku-musi
inkbunny: https://inkbunny.net/s/3010010
sofurry: https://www.sofurry.com/view/1992374
sofurrybeta: https://sofurrybeta.com/s/4nAMWvAn
tags:
- oral vore
- ambiguous predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2305405/trouble-sleeping-vore-story
inkbunny: https://inkbunny.net/s/3124166
sofurry: https://www.sofurry.com/view/2051787
sofurrybeta: https://sofurrybeta.com/s/qmywZjQ1
tags:
- unbirth
- anthro predator

View file

@ -16,6 +16,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2291371/r-warped-friendship-vore-story
inkbunny: https://inkbunny.net/s/3088450
sofurry: https://www.sofurry.com/view/2035090
sofurrybeta: https://sofurrybeta.com/s/AnKr5QPm
tags:
- oral vore
- anthro predator

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2331157/c-within-limits
inkbunny: https://inkbunny.net/s/3191950
sofurry: https://www.sofurry.com/view/2079346
sofurrybeta: https://sofurrybeta.com/s/J1O5Z3km
tags:
- unbirth
- cock vore

View file

@ -19,6 +19,7 @@ posts:
furaffinity: https://www.furaffinity.net/view/57459921/
inkbunny: https://inkbunny.net/s/3377722
sofurry: https://www.sofurry.com/view/2156237
sofurrybeta: https://sofurrybeta.com/s/rm8DBRWm
weasyl: https://www.weasyl.com/~badmanners/submissions/2396278/woofer-exploration
mastodon: https://meow.social/@BadManners/112827104119035982
tags:

View file

@ -14,6 +14,7 @@ posts:
weasyl: https://www.weasyl.com/~badmanners/submissions/2245815/you-re-home-vore-story
inkbunny: https://inkbunny.net/s/3009942
sofurry: https://www.sofurry.com/view/1992304
sofurrybeta: https://sofurrybeta.com/s/RnoXY6o1
tags:
- oral vore
- anthro predator

View file

@ -65,5 +65,9 @@ tags:
- cock vore
- name: soul vore
description: Scenarios where predators consume a soul instead of their prey's body.
- name: endo trait theft
description: Scenarios where predators acquire traits from their prey without digestion.
related:
- transformation
- name: Vore Day
description: Stories created in commemoration of Vore Day, which is celebrated on August 8th, and/or are set in said day.

View file

@ -9,6 +9,8 @@ tags:
- object vore
- name: transformation
description: Scenarios where a character changes body and/or species.
related:
- endo trait theft
- name: netorare
description: Scenarios involving a character cheating on their partner with or without consent, and/or cuckoldry.
- name: sizeplay

View file

@ -10,6 +10,7 @@ links:
sofurry:
link: https://bad-manners.sofurry.com/
username: Bad Manners
sofurrybeta: https://sofurrybeta.com/u/BadManners
weasyl: https://www.weasyl.com/~BadManners
twitter: https://twitter.com/BadManners__
mastodon: https://meow.social/@BadManners

View file

@ -51,6 +51,34 @@ author = "Noto Project Authors"
source = "https://github.com/notofonts/latin-greek-cyrillic"
license = { name = "SIL Open Font License v1.1", url = "https://opensource.org/license/ofl-1-1" }
[[attributions]]
author = "Simple Icons"
description = "Icons for third-party brands."
type = "icons"
source = "https://simpleicons.org"
license = { name = "CC0 1.0 Universal", url = "https://creativecommons.org/publicdomain/zero/1.0/" }
items = [
"Weasyl",
]
notes = """All third-party copyrights and trademarks belong to their respective owners, \
and I'm not affiliated with any of them."""
[[attributions]]
description = "Edited icons for other websites."
type = "icons"
author = { name = "Bad Manners", url = "https://badmanners.xyz", email = "me@badmanners.xyz>" }
source = "https://git.badmanners.xyz/badmanners/badmanners.xyz/src/branch/main/src/components/icons/brands"
license = { name = "CC0 1.0 Universal", url = "https://creativecommons.org/publicdomain/zero/1.0/" }
items = [
"Eka's Portal",
"Fur Affinity",
"Inkbunny",
"SoFurry",
]
notes = """Original icons edited for personal use and released under a permissive license.
All third-party copyrights and trademarks belong to their respective owners, \
and I'm not affiliated with any of them."""
[[attributions]]
author = "Font Awesome"
description = "Generic icons."

View file

@ -113,6 +113,30 @@ const UI_STRINGS = {
en: "Click to reveal",
tok: "Click to reveal summary in English",
},
"published_content/syndication_see_also_on": {
en: "See also on...",
tok: "o lukin lon ma ante",
},
"published_content/syndication_eka": {
en: "Eka's Portal",
tok: "lipu Ikapoto",
},
"published_content/syndication_furaffinity": {
en: "Fur Affinity",
tok: "lipu Panapinisi",
},
"published_content/syndication_inkbunny": {
en: "Inkbunny",
tok: "lipu Inpani",
},
"published_content/syndication_sofurry": {
en: "SoFurry",
tok: "lipu Sopanli",
},
"published_content/syndication_weasyl": {
en: "Weasyl",
tok: "lipu Wise",
},
// Story page-specific strings
"story/return_to_stories": {
en: "Return to stories",

View file

@ -7,9 +7,10 @@ import AgeRestrictedModal from "../components/AgeRestrictedModal.astro";
type Props = {
pageTitle: string;
lang?: string;
isAgeRestricted: boolean;
};
const { pageTitle, lang = "en" } = Astro.props;
const { pageTitle, lang = "en", isAgeRestricted } = Astro.props;
---
<html lang={lang}>
@ -38,6 +39,6 @@ const { pageTitle, lang = "en" } = Astro.props;
<body>
<slot />
<DarkModeScript />
<AgeRestrictedModal />
{isAgeRestricted ? <AgeRestrictedModal /> : null}
</body>
</html>

View file

@ -17,18 +17,19 @@ import {
type Props = {
pageTitle: string;
isAgeRestricted?: boolean;
enablePagefind?: boolean;
class?: string;
};
const { pageTitle, enablePagefind, class: className } = Astro.props;
const { pageTitle, enablePagefind, class: className, isAgeRestricted } = Astro.props;
const logo = await getImage({ src: logoBM, width: 192 });
const currentYear = new Date().getFullYear().toString();
const isCurrentRoute = (path: string) =>
Astro.url.pathname === path || (path !== "/" && Astro.url.pathname === `${path}/`);
---
<BaseLayout pageTitle={pageTitle}>
<BaseLayout pageTitle={pageTitle} isAgeRestricted={isAgeRestricted ?? true}>
<Fragment slot="head">
<meta property="og:title" content={pageTitle || "Bad Manners"} />
<meta property="og:url" content={Astro.url} />

View file

@ -22,6 +22,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
title={props.title}
lang={props.lang}
isDraft={props.isDraft}
isAgeRestricted={props.isAgeRestricted}
pubDate={props.pubDate}
description={props.description}
summary={undefined}

View file

@ -20,6 +20,7 @@ import {
IconChevronRight,
IconArrowUp,
} from "../components/icons";
import ExternalPosts from "../components/ExternalPosts.astro";
interface RelatedContent {
link: string;
@ -31,6 +32,7 @@ type Props = {
title: string;
lang: Lang;
isDraft: boolean;
isAgeRestricted: boolean;
pubDate?: Date;
description: string;
summary?: string;
@ -88,7 +90,7 @@ const returnTo = series
: props.labelReturnTo.title;
---
<BaseLayout pageTitle={props.title} lang={props.lang}>
<BaseLayout pageTitle={props.title} lang={props.lang} isAgeRestricted={props.isAgeRestricted}>
<Fragment slot="head">
{props.isDraft ? <meta name="robots" content="noindex" /> : null}
<meta property="og:title" content={props.title} data-pagefind-meta="title[content]" />
@ -153,11 +155,11 @@ const returnTo = series
<a
href={props.prev.link}
rel="prev"
class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 underline dark:border-stone-600"
class="h-entry u-url text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 underline dark:border-stone-600"
aria-label={props.labelPreviousContent}
>
<IconChevronLeft width="1.25rem" height="1.25rem" />
<span class="ml-1">{props.prev.title}</span>
<span class="p-name ml-1">{props.prev.title}</span>
</a>
) : (
<div class="h-full border-r border-stone-400 dark:border-stone-600" aria-hidden="true" />
@ -166,10 +168,10 @@ const returnTo = series
<a
href={props.next.link}
rel="next"
class="text-link flex items-center justify-center px-1 py-3 underline"
class="h-entry u-url text-link flex items-center justify-center px-1 py-3 underline"
aria-label={props.labelNextContent}
>
<span class="mr-1">{props.next.title}</span>
<span class="p-name mr-1">{props.next.title}</span>
<IconChevronRight width="1.25rem" height="1.25rem" />
</a>
) : (
@ -376,6 +378,7 @@ const returnTo = series
</section>
) : null
}
<ExternalPosts lang={props.lang} posts={props.posts} />
{props.posts.mastodon ? <MastodonComments lang={props.lang} {...props.posts.mastodon} /> : null}
</main>
<div

View file

@ -27,6 +27,7 @@ const wordCount = props.wordCount?.toString();
title={props.title}
lang={props.lang}
isDraft={props.isDraft}
isAgeRestricted={props.isAgeRestricted}
pubDate={props.pubDate}
description={props.description}
summary={props.summary}

View file

@ -79,8 +79,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" />
<div class="p-summary">
<p class="my-4">
Glad to see you here. You can expect lots of safe vore/endosoma ahead. Use the navigation menu to navigate through
my content.
Hey there, welcome to my corner of the Internet! You can expect lots of safe vore/endosoma ahead.
</p>
<ul class="list-disc pl-8">
<li><a class="text-link underline" href="/stories/1">Read my stories!</a></li>

View file

@ -80,6 +80,13 @@ export function getWebsiteLinkForUser(
return `ib!${getUserDataForWebsite(user, "inkbunny").username}`;
}
break;
case "sofurrybeta":
if ("sofurrybeta" in links) {
return `@${links.sofurrybeta!.username}`;
} else if ("sofurry" in links) {
return `[${links.sofurry!.username}](${links.sofurry!.link})`;
}
break;
case "twitter":
if ("twitter" in links) {
return `@${getUserDataForWebsite(user, "twitter").username}`;

View file

@ -1,5 +1,5 @@
import { getCollection, getEntry } from "astro:content";
import type { Lang, PostWebsite } from "../content/config";
import type { Lang, PostWebsite, UserWebsite } from "../content/config";
import { getWebsiteLinkForUser } from "./get_website_link_for_user";
import { getUsernameForLang } from "./get_username_for_lang";
@ -56,8 +56,8 @@ export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lan
if (!story) {
throw new Error(`Couldn't find story with slug "${slug}"`);
}
if (typeof website === "string" && story.data.posts[website]?.link) {
replacements.push(`[${text}](${story.data.posts[website].link})`);
if (typeof website === "string" && story.data.posts[website as PostWebsite]?.link) {
replacements.push(`[${text}](${story.data.posts[website as PostWebsite]!.link})`);
continue;
}
break;
@ -66,8 +66,8 @@ export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lan
if (!game) {
throw new Error(`Couldn't find game with slug "${slug}"`);
}
if (typeof website === "string" && game.data.posts[website]?.link) {
replacements.push(`[${text}](${game.data.posts[website].link})`);
if (typeof website === "string" && game.data.posts[website as PostWebsite]?.link) {
replacements.push(`[${text}](${game.data.posts[website as PostWebsite]!.link})`);
continue;
}
break;
@ -78,8 +78,8 @@ export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lan
}
// If there's a label in the link, use that if possible
if (text) {
if (typeof website === "string" && user.data.links[website]?.link) {
replacements.push(`[${text}](${user.data.links[website].link})`);
if (typeof website === "string" && user.data.links[website as UserWebsite]?.link) {
replacements.push(`[${text}](${user.data.links[website as UserWebsite]!.link})`);
continue;
} else if (user.data.preferredLink) {
replacements.push(`[${text}](${user.data.links[user.data.preferredLink]!.link})`);
@ -87,7 +87,9 @@ export async function qualifyLocalURLsInMarkdown(originalText: string, lang: Lan
}
}
// Otherwise (i.e. label is empty), use the username for the website
replacements.push(getWebsiteLinkForUser(user, website, (user) => getUsernameForLang(user, lang)));
replacements.push(
getWebsiteLinkForUser(user, website as UserWebsite, (user) => getUsernameForLang(user, lang)),
);
continue;
default:
const unknown: never = contentPrefix;