Improve age-restricted hyperlinks, clean up markup, and add navigation icons
This commit is contained in:
parent
c55c82633d
commit
57c2c7c649
31 changed files with 279 additions and 138 deletions
package-lock.jsonpackage.json
scripts
src
components
AgeRestrictedModal.astroAgeRestrictedScriptInline.astroCopyrightedCharacters.astroDarkModeScript.astroMastodonComments.astroNavigation.astroUserComponent.astro
icons
content
stories
tag-categories
layouts
pages
utils
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -16,6 +16,7 @@
|
|||
"@tailwindcss/typography": "^0.5.14",
|
||||
"astro": "^4.13.3",
|
||||
"astro-pagefind": "^1.6.0",
|
||||
"clsx": "^2.1.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"marked": "^14.0.0",
|
||||
"pagefind": "^1.1.0",
|
||||
|
@ -5046,11 +5047,12 @@
|
|||
]
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"@tailwindcss/typography": "^0.5.14",
|
||||
"astro": "^4.13.3",
|
||||
"astro-pagefind": "^1.6.0",
|
||||
"clsx": "^2.1.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"marked": "^14.0.0",
|
||||
"pagefind": "^1.1.0",
|
||||
|
|
|
@ -17,7 +17,7 @@ async function deployLftp({ host, user, password, targetFolder, sourceFolder, as
|
|||
[
|
||||
"-c",
|
||||
[
|
||||
`open -u ${user},${password} ${host}`,
|
||||
`open -u ${user},${password.replaceAll(/([ \t.,:;?!`'"^|*+#&$\(\)\[\]{}<>\\/-])/, "\\$1")} ${host}`,
|
||||
`mirror --reverse --include-glob ${join(assetsFolder, "*")} --delete --only-missing --no-perms --verbose ${n(sourceFolder)} ${n(targetFolder)}`,
|
||||
`mirror --reverse --exclude-glob ${join(assetsFolder, "*")} --delete --no-perms --verbose ${n(sourceFolder)} ${n(targetFolder)}`,
|
||||
`bye`,
|
||||
|
@ -27,9 +27,9 @@ async function deployLftp({ host, user, password, targetFolder, sourceFolder, as
|
|||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
process.on("close", (code) =>
|
||||
(code === 0) ? resolve() : reject(`lftp failed with code ${code}`),
|
||||
(code === 0) ? resolve(0) : reject(`lftp failed with code ${code}`),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -66,20 +66,20 @@ async function exportStory(slug: string, options: { outputDir: string }) {
|
|||
/* Spawn Astro dev server */
|
||||
console.log("Starting Astro development server...");
|
||||
const devServerProcess = spawn("./node_modules/.bin/astro", ["dev"], { stdio: 'pipe' });
|
||||
const promise = new Promise<string>((resolve, reject) => {
|
||||
const localServerRegex = /Local\s+(http\S+)/
|
||||
const lines = createInterface({ input: devServerProcess.stdout });
|
||||
lines.on("line", (line) => {
|
||||
const match = localServerRegex.exec(line);
|
||||
if (match && match[1]) {
|
||||
resolve(match[1]);
|
||||
}
|
||||
});
|
||||
lines.on("close", reject);
|
||||
});
|
||||
const astroURL = await promise;
|
||||
console.log(`Astro listening on ${astroURL}`);
|
||||
let astroURL: string | null = null;
|
||||
try {
|
||||
astroURL = await new Promise<string>((resolve, reject) => {
|
||||
const localServerRegex = /Local\s+(http:\/\/\S+)/
|
||||
const lines = createInterface({ input: devServerProcess.stdout });
|
||||
lines.on("line", (line) => {
|
||||
const match = localServerRegex.exec(line);
|
||||
if (match && match[1]) {
|
||||
resolve(match[1]);
|
||||
}
|
||||
});
|
||||
lines.on("close", reject);
|
||||
});
|
||||
console.log(`Astro listening on ${astroURL}`);
|
||||
const response = await fetchRetry(new URL(`/api/healthcheck`, astroURL), { retries: 5, retryDelay: 2000 });
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
|
@ -106,9 +106,9 @@ async function exportStory(slug: string, options: { outputDir: string }) {
|
|||
}
|
||||
const data: { story: string, description: Record<string, string>, thumbnail: string | null } = await response.json();
|
||||
await Promise.all(
|
||||
Object.entries(data.description).map(async ([website, description]) => {
|
||||
Object.entries(data.description).map(async ([filename, description]) => {
|
||||
return await writeFile(
|
||||
join(outputDir, `description_${website}.${website === "weasyl" ? "md" : "txt"}`),
|
||||
join(outputDir, filename),
|
||||
description,
|
||||
);
|
||||
}),
|
||||
|
@ -124,7 +124,7 @@ async function exportStory(slug: string, options: { outputDir: string }) {
|
|||
if (!thumbnail.ok) {
|
||||
throw new Error("Failed to get thumbnail");
|
||||
}
|
||||
const thumbnailExt = thumbnail.headers.get("Content-Type")?.startsWith("image/png") ? "png" : "jpg";
|
||||
const thumbnailExt = thumbnail.headers.get("Content-Type")!.startsWith("image/png") ? "png" : "jpg";
|
||||
await writeFile(join(outputDir, `thumbnail.${thumbnailExt}`), Buffer.from(await thumbnail.arrayBuffer()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
|
|||
</p>
|
||||
<div class="flex w-full max-w-md flex-col-reverse justify-evenly gap-y-5 px-6 pt-5 sm:max-w-2xl sm:flex-row">
|
||||
<button
|
||||
style={{display: "none"}}
|
||||
data-modal-reject
|
||||
id="age-verification-reject"
|
||||
class="rounded bg-stone-400 py-3 text-lg text-stone-900 hover:bg-stone-500 hover:text-stone-50 focus:bg-stone-500 focus:text-stone-50 sm:px-9 dark:bg-stone-300 dark:text-stone-900 dark:hover:bg-stone-600 dark:hover:text-stone-50 dark:focus:bg-stone-600 dark:focus:text-stone-50"
|
||||
|
@ -35,6 +36,7 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
|
|||
Cancel
|
||||
</button>
|
||||
<button
|
||||
style={{display: "none"}}
|
||||
data-modal-accept
|
||||
id="age-verification-accept"
|
||||
class="rounded bg-bm-500 py-3 text-lg text-stone-900 hover:bg-stone-500 hover:text-stone-50 focus:bg-stone-500 focus:text-stone-50 sm:px-9 dark:bg-bm-400 dark:text-stone-900 dark:hover:bg-stone-600 dark:hover:text-stone-50 dark:focus:bg-stone-600 dark:focus:text-stone-50"
|
||||
|
@ -49,13 +51,18 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
|
|||
|
||||
<script>
|
||||
const ENABLE_VIEW_TRANSITIONS = false;
|
||||
type AgeVerified = "true" | undefined;
|
||||
|
||||
const ageRestrictedModalSetup = () => {
|
||||
const modal = document.querySelector<HTMLElementTagNameMap["div"]>("body > div#modal-age-restricted");
|
||||
const modal = document.querySelector<HTMLElementTagNameMap["div"]>("div#modal-age-restricted");
|
||||
// Not an age-restricted page
|
||||
if (!modal) {
|
||||
throw new Error("Missing #modal-age-restricted element! Make sure that it's a direct child of body.");
|
||||
return;
|
||||
}
|
||||
let ageVerified: "true" | undefined = localStorage.ageVerified;
|
||||
if (modal !== document.querySelector("body > div#modal-age-restricted")) {
|
||||
throw new Error("#modal-age-restricted must be a direct child of the body element!");
|
||||
}
|
||||
let ageVerified: AgeVerified = localStorage.ageVerified;
|
||||
if (ageVerified !== "true") {
|
||||
const rejectButton = modal.querySelector<HTMLElementTagNameMap["button"]>("button[data-modal-reject]")!;
|
||||
const onRejectButtonClick = (e: MouseEvent) => {
|
||||
|
@ -63,7 +70,9 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
|
|||
location.href = "about:blank";
|
||||
};
|
||||
rejectButton.addEventListener("click", onRejectButtonClick);
|
||||
modal.querySelector<HTMLElementTagNameMap["button"]>("button[data-modal-accept]")!.addEventListener(
|
||||
rejectButton.style.removeProperty("display");
|
||||
const acceptButton = modal.querySelector<HTMLElementTagNameMap["button"]>("button[data-modal-accept]")!;
|
||||
acceptButton.addEventListener(
|
||||
"click",
|
||||
(e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
@ -72,10 +81,16 @@ import IconTriangleExclamation from "./icons/IconTriangleExclamation.astro";
|
|||
localStorage.ageVerified = ageVerified;
|
||||
document.body.style.overflow = "auto";
|
||||
document.querySelectorAll("body > :not(#modal-age-restricted)").forEach((el) => el.removeAttribute("inert"));
|
||||
document.body.querySelectorAll<HTMLElementTagNameMap["a"]>("a[href][data-age-restricted]").forEach((el) => {
|
||||
let newHref = new URL(el.href);
|
||||
newHref.searchParams.set("ageVerified", "true");
|
||||
el.href = newHref.href;
|
||||
});
|
||||
modal.style.display = "none";
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
acceptButton.style.removeProperty("display");
|
||||
rejectButton.focus();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
---
|
||||
---
|
||||
|
||||
<script is:inline>function a(){let b=document,c="#modal-age-restricted",d="true";localStorage.ageVerified!==d&&((b.body.style.overflow="hidden"),b.querySelectorAll("body > :not("+c+")").forEach(e=>e.setAttribute("inert",d)),(b.querySelector("body > "+c).style.display="block"));}document.addEventListener("astro:after-swap",a);a()</script>
|
||||
<script is:inline>function a(){let b=document,c="#modal-age-restricted",d="true",e=b.querySelector("body > "+c),f="ageVerified",g="searchParams",h=localStorage;new URL(b.location)[g].get(f)===d&&(h[f]=d);e&&(h[f]===d?b.querySelectorAll("a[href][data-age-restricted]").forEach(x=>{let y=new URL(x.href);y[g].set(f,d);x.href=y.href}):((b.body.style.overflow="hidden"),b.querySelectorAll("body > :not("+c+")").forEach(x=>x.setAttribute("inert",d)),(e.style.display="block")))};document.addEventListener("astro:after-swap",a);a()</script>
|
||||
|
|
|
@ -24,7 +24,7 @@ const charactersPerUser = copyrightedCharacters ? await formatCopyrightedCharact
|
|||
t(lang, "characters/characters_are_copyrighted_by", user, characters[0] === "" ? [] : characters)
|
||||
}
|
||||
>
|
||||
<UserComponent class="p-name" lang={lang} user={user} />
|
||||
<UserComponent lang={lang} user={user} />
|
||||
</CopyrightedCharactersItem>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
@ -6,10 +6,10 @@ import DarkModeScriptInline from "./DarkModeScriptInline.astro";
|
|||
|
||||
<script>
|
||||
const ENABLE_VIEW_TRANSITIONS = false;
|
||||
type ColorScheme = "auto" | "dark" | "light";
|
||||
type ColorScheme = "auto" | "dark" | "light" | undefined;
|
||||
|
||||
const colorSchemeSetup = () => {
|
||||
let colorScheme: ColorScheme | undefined = localStorage.colorScheme;
|
||||
let colorScheme: ColorScheme = localStorage.colorScheme;
|
||||
if (!colorScheme || colorScheme === "auto") {
|
||||
colorScheme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ import DarkModeScriptInline from "./DarkModeScriptInline.astro";
|
|||
localStorage.colorScheme = colorScheme;
|
||||
};
|
||||
document.querySelectorAll<HTMLElementTagNameMap["button"]>("button[data-dark-mode]").forEach((button) => {
|
||||
button.addEventListener("click", toggleColorScheme);
|
||||
button.classList.remove("hidden");
|
||||
button.style.removeProperty("display");
|
||||
button.setAttribute("aria-hidden", "false");
|
||||
button.addEventListener("click", toggleColorScheme);
|
||||
});
|
||||
};
|
||||
if (ENABLE_VIEW_TRANSITIONS) {
|
||||
|
|
|
@ -84,6 +84,7 @@ const { link, instance, user, postId } = Astro.props;
|
|||
<a
|
||||
data-author
|
||||
class="p-author h-card u-url text-link flex items-center text-lg hover:underline focus:underline"
|
||||
rel="nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
<picture>
|
||||
|
@ -95,6 +96,7 @@ const { link, instance, user, postId } = Astro.props;
|
|||
<a
|
||||
data-post-link
|
||||
class="u-url text-link my-1 flex items-center text-sm font-light hover:underline focus:underline"
|
||||
rel="nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
<time class="dt-published mr-1" data-published-date aria-label="Publish date"></time>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/">Home</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/stories/1">Stories</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/games">Games</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/tags">Tags</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/search">Search</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import clsx from "clsx";
|
||||
import type { Lang } from "../i18n";
|
||||
import { getUsernameForLang } from "../utils/get_username_for_lang";
|
||||
|
||||
|
@ -17,10 +18,10 @@ const link = user.data.preferredLink ? user.data.links[user.data.preferredLink]!
|
|||
|
||||
{
|
||||
link ? (
|
||||
<a rel={rel} href={link} class:list={[className, "h-card u-url text-link underline"]} target="_blank">
|
||||
<a rel={clsx(rel, "nofollow")} href={link} class:list={[className, "h-card u-url p-name text-link underline"]} target="_blank">
|
||||
{username}
|
||||
</a>
|
||||
) : (
|
||||
<span class:list={[className, "h-card"]}>{username}</span>
|
||||
<span class:list={[className, "h-card p-name"]}>{username}</span>
|
||||
)
|
||||
}
|
||||
|
|
13
src/components/icons/IconBook.astro
Normal file
13
src/components/icons/IconBook.astro
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 448 512">
|
||||
<path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z" />
|
||||
</SVGIcon>
|
15
src/components/icons/IconBriefcase.astro
Normal file
15
src/components/icons/IconBriefcase.astro
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M184 48l144 0c4.4 0 8 3.6 8 8l0 40L176 96l0-40c0-4.4 3.6-8 8-8zm-56 8l0 40L64 96C28.7 96 0 124.7 0 160l0 96 192 0 128 0 192 0 0-96c0-35.3-28.7-64-64-64l-64 0 0-40c0-30.9-25.1-56-56-56L184 0c-30.9 0-56 25.1-56 56zM512 288l-192 0 0 32c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32l0-32L0 288 0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-128z"
|
||||
></path>
|
||||
</SVGIcon>
|
13
src/components/icons/IconGamepad.astro
Normal file
13
src/components/icons/IconGamepad.astro
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 640 512">
|
||||
<path d="M192 64C86 64 0 150 0 256S86 448 192 448l256 0c106 0 192-86 192-192s-86-192-192-192L192 64zM496 168a40 40 0 1 1 0 80 40 40 0 1 1 0-80zM392 304a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zM168 200c0-13.3 10.7-24 24-24s24 10.7 24 24l0 32 32 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-32 0 0 32c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-32-32 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l32 0 0-32z" />
|
||||
</SVGIcon>
|
13
src/components/icons/IconMagnifyingGlass.astro
Normal file
13
src/components/icons/IconMagnifyingGlass.astro
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 512 512">
|
||||
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z" />
|
||||
</SVGIcon>
|
14
src/components/icons/IconTags.astro
Normal file
14
src/components/icons/IconTags.astro
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import SVGIcon from "./SVGIcon.astro";
|
||||
|
||||
type Props = {
|
||||
width: string;
|
||||
height: string;
|
||||
class?: string;
|
||||
};
|
||||
---
|
||||
|
||||
<SVGIcon {...Astro.props} viewBox="0 0 512 512">
|
||||
<path d="M345 39.1L472.8 168.4c52.4 53 52.4 138.2 0 191.2L360.8 472.9c-9.3 9.4-24.5 9.5-33.9 .2s-9.5-24.5-.2-33.9L438.6 325.9c33.9-34.3 33.9-89.4 0-123.7L310.9 72.9c-9.3-9.4-9.2-24.6 .2-33.9s24.6-9.2 33.9 .2zM0 229.5L0 80C0 53.5 21.5 32 48 32l149.5 0c17 0 33.3 6.7 45.3 18.7l168 168c25 25 25 65.5 0 90.5L277.3 442.7c-25 25-65.5 25-90.5 0l-168-168C6.7 262.7 0 246.5 0 229.5zM144 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||
/>
|
||||
</SVGIcon>
|
|
@ -9,7 +9,7 @@ thumbnail: /src/assets/thumbnails/bm_10_birdroom.png
|
|||
description: |
|
||||
Beetle finds an odd-shaped friend deep in his work, and does what he does best: be a messy distraction.
|
||||
|
||||
This silly short was inspired by [a wonderful commission](https://booru.badmanners.xyz/index.php?q=post/view/4) that I got from [Eli-Eternity](https://www.furaffinity.net/user/eli-eternity) and was supposed to go along with it, but this story doesn't even come close to doing it justice. Seriously, check out his piece if you haven't! It's turned out amazing, and he's done an incredible job, and I love it so much!
|
||||
This silly short was inspired by <a href="https://booru.badmanners.xyz/index.php?q=post/view/4" target="_blank" data-age-restricted>a wonderful commission</a> that I got from [Eli-Eternity](https://www.furaffinity.net/user/eli-eternity) and was supposed to go along with it, but this story doesn't even come close to doing it justice. Seriously, check out his piece if you haven't! It's turned out amazing, and he's done an incredible job, and I love it so much!
|
||||
|
||||
Why are you still reading this instead of clicking the link above?!
|
||||
posts:
|
||||
|
|
|
@ -9,7 +9,7 @@ thumbnail: /src/assets/thumbnails/bm_8_flavorful_favor.png
|
|||
description: |
|
||||
One, fearful and furry; one, formidable and feathery; and one, fired-up for the follow-up. A threesome fatefully forced into a fantastical face-to-face.
|
||||
|
||||
So many ideas that I want to write...! I just haven't found a lot of motivation to write as much as before. But I'm still working, and haven't gone anywhere. At least I finally managed to churn out this one, featuring Beetle, one of my OCs! You might recognize him from [one of the few things](https://booru.badmanners.xyz/index.php?q=post/view/3) I actually uploaded this last month. Quite a fun personality to play with, along with my sona Sam and another OC, Muno - someone might remember that last name from one of my previous stories. All of my characters featured in stories are fun really, with a few that I wanna revisit or new ones that I make for each new setting. Just wish I had a better writing rhythm to put all those ideas to life like I used to, though! Managing to finish this story is a start, I guess.
|
||||
So many ideas that I want to write...! I just haven't found a lot of motivation to write as much as before. But I'm still working, and haven't gone anywhere. At least I finally managed to churn out this one, featuring Beetle, one of my OCs! You might recognize him from <a href="https://booru.badmanners.xyz/index.php?q=post/view/3" target="_blank" data-age-restricted>one of the few things</a> I actually uploaded this last month. Quite a fun personality to play with, along with my sona Sam and another OC, Muno - someone might remember that last name from one of my previous stories. All of my characters featured in stories are fun really, with a few that I wanna revisit or new ones that I make for each new setting. Just wish I had a better writing rhythm to put all those ideas to life like I used to, though! Managing to finish this story is a start, I guess.
|
||||
posts:
|
||||
eka: https://aryion.com/g4/view/884215
|
||||
furaffinity: https://www.furaffinity.net/view/51778522/
|
||||
|
|
|
@ -9,7 +9,7 @@ thumbnail: /src/assets/thumbnails/bm_ff_14_part_of_the_show.png
|
|||
description: |
|
||||
You attend a show, unaware of how personal it can turn out...
|
||||
|
||||
This is a story based off of a YCH that I got from [Helkumurrr](https://www.furaffinity.net/user/helkumurrr) which [you should definitely check out](https://booru.badmanners.xyz/index.php?q=post/view/5). First piece with myself as pred, to complement my previous commission where I was prey – and also with a short (and hopefully fun) story to go along with it!
|
||||
This is a story based off of a YCH that I got from [Helkumurrr](https://www.furaffinity.net/user/helkumurrr) which <a href="https://booru.badmanners.xyz/index.php?q=post/view/5" target="_blank" data-age-restricted>you should definitely check out</a>. First piece with myself as pred, to complement my previous commission where I was prey – and also with a short (and hopefully fun) story to go along with it!
|
||||
posts:
|
||||
eka: https://aryion.com/g4/view/902197
|
||||
furaffinity: https://www.furaffinity.net/view/52509501/
|
||||
|
|
|
@ -2,20 +2,20 @@ name: Types of vore
|
|||
index: 1
|
||||
tags:
|
||||
- name: { en: oral vore, tok: moku musi kepeken uta }
|
||||
description: Scenarios where prey are consumed by the predator through their mouth.
|
||||
description: Scenarios where prey are consumed by the predator's mouth.
|
||||
- name: anal vore
|
||||
description: Scenarios where prey are consumed by the predator through their butt/anus.
|
||||
description: Scenarios where prey are consumed by the predator's butt/anus.
|
||||
- name: cock vore
|
||||
description: Scenarios where prey are consumed by the predator through their penis.
|
||||
description: Scenarios where prey are consumed by the predator's penis.
|
||||
- name: unbirth
|
||||
description: Scenarios where prey are consumed by the predator through their vagina/vulva, sometimes also called "vaginal vore".
|
||||
description: Scenarios where prey are consumed by the predator's vagina/vulva, sometimes also called "vaginal vore".
|
||||
- name: tail vore
|
||||
description: Scenarios where prey are consumed by the predator through an opening or mouth in their tail.
|
||||
description: Scenarios where prey are consumed by the predator's tail, through an opening or mouth on the appendage.
|
||||
- name: slit vore
|
||||
description: Scenarios where prey are consumed by the predator through a genital slit.
|
||||
description: Scenarios where prey are consumed by the predator's genital slit.
|
||||
- name: sheath vore
|
||||
description: Scenarios where prey are consumed by the predator through a genital sheath, generally around their penis.
|
||||
description: Scenarios where prey are consumed by the predator's genital sheath around their penis.
|
||||
- name: nipple vore
|
||||
description: Scenarios where prey are consumed by the predator through their nipple/breast.
|
||||
description: Scenarios where prey are consumed by the predator's breast, through the nipples or cleavage.
|
||||
- name: chest maw vore
|
||||
description: Scenarios where prey are consumed by the predator through a mouth/maw on their chest cavity.
|
||||
description: Scenarios where prey are consumed by the predator's chest, through a maw on their chest cavity.
|
||||
|
|
|
@ -2,8 +2,8 @@ name: Recurring characters
|
|||
index: 10
|
||||
tags:
|
||||
- name: Sam Brendan
|
||||
description: Content that includes my character and fursona [Sam, the mimic x maned wolf hybrid](https://badmanners.xyz/sam_brendan).
|
||||
description: Content that includes my character and fursona <a href="https://badmanners.xyz/sam_brendan" target="_blank" data-age-restricted>Sam, the mimic x maned wolf hybrid</a>.
|
||||
- name: Beetle
|
||||
description: Content that includes my character [Beetle, the gryphon](https://booru.badmanners.xyz/index.php?q=post/view/3).
|
||||
description: Content that includes my character <a href="https://booru.badmanners.xyz/index.php?q=post/view/3" target="_blank" data-age-restricted>Beetle, the gryphon</a>.
|
||||
- name: Muno
|
||||
description: Content that includes my character Muno, the snake.
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
---
|
||||
import { getImage } from "astro:assets";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Navigation from "../components/Navigation.astro";
|
||||
import logoBM from "../assets/images/logo_bm.png";
|
||||
import { t } from "../i18n";
|
||||
import IconHome from "../components/icons/IconHome.astro";
|
||||
import IconBriefcase from "../components/icons/IconBriefcase.astro";
|
||||
import IconSquareRSS from "../components/icons/IconSquareRSS.astro";
|
||||
import IconSun from "../components/icons/IconSun.astro";
|
||||
import IconMoon from "../components/icons/IconMoon.astro";
|
||||
import IconMagnifyingGlass from "../components/icons/IconMagnifyingGlass.astro";
|
||||
import IconTags from "../components/icons/IconTags.astro";
|
||||
import IconGamepad from "../components/icons/IconGamepad.astro";
|
||||
import IconBook from "../components/icons/IconBook.astro";
|
||||
|
||||
type Props = {
|
||||
pageTitle: string;
|
||||
|
@ -18,6 +22,8 @@ type Props = {
|
|||
const { pageTitle, enablePagefind, class: className } = 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}>
|
||||
|
@ -31,8 +37,8 @@ const currentYear = new Date().getFullYear().toString();
|
|||
<div
|
||||
class="flex min-h-screen flex-col bg-stone-200 text-stone-800 md:flex-row dark:bg-stone-800 dark:text-stone-200 print:bg-none"
|
||||
>
|
||||
<div
|
||||
class="h-card static mb-4 flex flex-col items-center bg-bm-300 pt-10 text-center text-stone-900 shadow-xl md:fixed md:inset-y-0 md:left-0 md:mb-0 md:w-60 md:pt-20 dark:bg-green-900 dark:text-stone-100 print:bg-none print:shadow-none"
|
||||
<nav
|
||||
class="h-card static mb-4 flex flex-col items-center bg-bm-300 pt-10 pb-10 text-center text-stone-900 shadow-xl md:fixed md:inset-y-0 md:left-0 md:mb-0 md:w-60 md:pt-20 dark:bg-green-900 dark:text-stone-100 print:bg-none print:shadow-none"
|
||||
>
|
||||
<img
|
||||
loading="eager"
|
||||
|
@ -42,7 +48,90 @@ const currentYear = new Date().getFullYear().toString();
|
|||
width={192}
|
||||
/>
|
||||
<span class="p-name my-2 text-2xl font-semibold">Bad Manners</span>
|
||||
<Navigation />
|
||||
<ul class="flex flex-col gap-y-2">
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="https://badmanners.xyz/"
|
||||
data-age-restricted
|
||||
rel="me"
|
||||
>
|
||||
<IconHome width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Main website</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/"
|
||||
aria-current={isCurrentRoute("/") ? "page" : undefined}
|
||||
>
|
||||
<IconBriefcase width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Gallery</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/stories/1"
|
||||
aria-current={isCurrentRoute("/stories/1") ? "page" : undefined}
|
||||
>
|
||||
<IconBook width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Stories</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/games"
|
||||
aria-current={isCurrentRoute("/games") ? "page" : undefined}
|
||||
>
|
||||
<IconGamepad width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Games</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/tags"
|
||||
aria-current={isCurrentRoute("/tags") ? "page" : undefined}
|
||||
>
|
||||
<IconTags width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Tags</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/search"
|
||||
rel="search"
|
||||
aria-current={isCurrentRoute("/search") ? "page" : undefined}
|
||||
>
|
||||
<IconMagnifyingGlass width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">Search</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="u-url group text-link"
|
||||
href="/feed.xml"
|
||||
>
|
||||
<IconSquareRSS width="1.25rem" height="1.25rem" class="inline align-middle" />
|
||||
<span class="group-focus:underline group-hover:underline">RSS feed</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
data-dark-mode
|
||||
style={{ display: "none" }}
|
||||
class="group text-link"
|
||||
>
|
||||
<IconSun width="1.25rem" height="1.25rem" class="align-middle hidden dark:inline" />
|
||||
<IconMoon width="1.25rem" height="1.25rem" class="inline align-middle dark:hidden" />
|
||||
<span class="group-focus:underline group-hover:underline">{t("en", "published_content/toggle_dark_mode")}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="pt-4 text-center text-xs text-black dark:text-white">
|
||||
<span
|
||||
>© {
|
||||
|
@ -55,29 +144,9 @@ const currentYear = new Date().getFullYear().toString();
|
|||
)
|
||||
} |
|
||||
</span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
<a class="text-link hover:underline focus:underline" href="/licenses.txt" rel="license">Licenses</a>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-x-1 pb-10">
|
||||
<a class="u-url text-link p-1" href="https://badmanners.xyz/" target="_blank" aria-labelledby="label-main-website">
|
||||
<IconHome width="1.5rem" height="1.5rem" />
|
||||
<span id="label-main-website" class="sr-only">Main website</span>
|
||||
</a>
|
||||
<a class="text-link p-1" href="/feed.xml" target="_blank" aria-labelledby="label-rss-feed">
|
||||
<IconSquareRSS width="1.5rem" height="1.5rem" />
|
||||
<span id="label-rss-feed" class="sr-only">RSS feed</span>
|
||||
</a>
|
||||
<button
|
||||
data-dark-mode
|
||||
style={{ display: "none" }}
|
||||
class="text-link p-1"
|
||||
aria-labelledby="label-toggle-dark-mode"
|
||||
>
|
||||
<IconSun width="1.5rem" height="1.5rem" class="hidden dark:block" />
|
||||
<IconMoon width="1.5rem" height="1.5rem" class="block dark:hidden" />
|
||||
<span id="label-toggle-dark-mode" class="sr-only">{t("en", "published_content/toggle_dark_mode")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main
|
||||
class:list={[className, "ml-0 max-w-6xl px-2 pb-12 pt-4 md:ml-60 md:px-4 print:pb-0"]}
|
||||
data-pagefind-body={enablePagefind ? "" : undefined}
|
||||
|
|
|
@ -54,7 +54,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
|
|||
/>
|
||||
<Fragment slot="section-information">
|
||||
<Authors lang={props.lang}>
|
||||
{authorsList.map((author) => <UserComponent rel="author" class="p-author" user={author} lang={props.lang} />)}
|
||||
{authorsList.map((author) => <UserComponent rel="author nofollow" class="p-author" user={author} lang={props.lang} />)}
|
||||
</Authors>
|
||||
<div id="platforms">
|
||||
<p>{t(props.lang, "game/platforms", props.platforms)}</p>
|
||||
|
|
|
@ -165,6 +165,7 @@ const thumbnail =
|
|||
{props.prev ? (
|
||||
<a
|
||||
href={props.prev.link}
|
||||
rel="prev"
|
||||
class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 font-light underline dark:border-stone-600"
|
||||
aria-label={props.labelPreviousContent}
|
||||
>
|
||||
|
@ -177,6 +178,7 @@ const thumbnail =
|
|||
{props.next ? (
|
||||
<a
|
||||
href={props.next.link}
|
||||
rel="next"
|
||||
class="text-link flex items-center justify-center px-1 py-3 font-light underline"
|
||||
aria-label={props.labelNextContent}
|
||||
>
|
||||
|
@ -299,6 +301,7 @@ const thumbnail =
|
|||
{props.prev ? (
|
||||
<a
|
||||
href={props.prev.link}
|
||||
rel="prev"
|
||||
class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 font-light underline dark:border-stone-600"
|
||||
aria-label={props.labelPreviousContent}
|
||||
>
|
||||
|
@ -311,6 +314,7 @@ const thumbnail =
|
|||
{props.next ? (
|
||||
<a
|
||||
href={props.next.link}
|
||||
rel="next"
|
||||
class="text-link flex items-center justify-center px-1 py-3 font-light underline"
|
||||
aria-label={props.labelNextContent}
|
||||
>
|
||||
|
@ -391,7 +395,7 @@ const thumbnail =
|
|||
<span
|
||||
set:html={t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())}
|
||||
/><span> |</span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank"
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" rel="license"
|
||||
>{t(props.lang, "published_content/licenses")}</a
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -71,7 +71,7 @@ const wordCount = props.wordCount?.toString();
|
|||
requestersList && (
|
||||
<Requesters lang={props.lang}>
|
||||
{requestersList.map((requester) => (
|
||||
<UserComponent class="p-name" user={requester} lang={props.lang} />
|
||||
<UserComponent user={requester} lang={props.lang} />
|
||||
))}
|
||||
</Requesters>
|
||||
)
|
||||
|
@ -80,7 +80,7 @@ const wordCount = props.wordCount?.toString();
|
|||
commissionersList && (
|
||||
<Commissioners lang={props.lang}>
|
||||
{commissionersList.map((commissioner) => (
|
||||
<UserComponent class="p-name" user={commissioner} lang={props.lang} />
|
||||
<UserComponent user={commissioner} lang={props.lang} />
|
||||
))}
|
||||
</Commissioners>
|
||||
)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { APIRoute, GetStaticPaths } from "astro";
|
||||
import { APACHE_CONFIG } from "astro:env/server";
|
||||
|
||||
const htaccess = `
|
||||
const htaccess = String.raw`
|
||||
ErrorDocument 404 /404.html
|
||||
RedirectMatch 301 ^/stories(\/(index.html)?)?$ /stories/1/
|
||||
RedirectMatch 301 ^/stories(/(index.html)?)?$ /stories/1/
|
||||
Redirect 301 /story/ /stories/
|
||||
Redirect 301 /game/ /games/
|
||||
`.trim();
|
||||
|
|
|
@ -90,9 +90,9 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
|||
}, Promise.resolve(""));
|
||||
switch (exportFormat) {
|
||||
case "bbcode":
|
||||
return { exportWebsite, description: markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n") };
|
||||
return { descriptionFilename: `description_${exportWebsite}.txt`, descriptionText: markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n") };
|
||||
case "markdown":
|
||||
return { exportWebsite, description: storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim() };
|
||||
return { descriptionFilename: `description_${exportWebsite}.md`, descriptionText: storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim() };
|
||||
default:
|
||||
const unknown: never = exportFormat;
|
||||
throw new Error(`Unknown export format "${unknown}"`);
|
||||
|
@ -131,10 +131,10 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
|||
story: storyText,
|
||||
description: description.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.exportWebsite] = item.description;
|
||||
acc[item.descriptionFilename] = item.descriptionText;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<PostWebsite, string>,
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
thumbnail: story.data.thumbnail ? story.data.thumbnail.src : null,
|
||||
}),
|
||||
|
|
|
@ -90,7 +90,9 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
|
|||
For more information about me, please check out <a
|
||||
class="text-link underline"
|
||||
href="https://badmanners.xyz/"
|
||||
target="_blank">my main website</a
|
||||
data-age-restricted
|
||||
rel="me"
|
||||
>my main website</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
|||
<div class="mx-auto mb-6 mt-2 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||||
{
|
||||
page.url.prev && (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={page.url.prev}>
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" rel="prev" href={page.url.prev}>
|
||||
Previous page
|
||||
</a>
|
||||
)
|
||||
|
@ -69,7 +69,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
|||
}
|
||||
{
|
||||
page.url.next && (
|
||||
<a class="text-link px-2 py-1 underline" href={page.url.next}>
|
||||
<a class="text-link px-2 py-1 underline" rel="next" href={page.url.next}>
|
||||
Next page
|
||||
</a>
|
||||
)
|
||||
|
@ -129,7 +129,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
|||
<div class="mx-auto my-6 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||||
{
|
||||
page.url.prev && (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={page.url.prev}>
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" rel="prev" href={page.url.prev}>
|
||||
Previous page
|
||||
</a>
|
||||
)
|
||||
|
@ -154,7 +154,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
|||
}
|
||||
{
|
||||
page.url.next && (
|
||||
<a class="text-link px-2 py-1 underline" href={page.url.next}>
|
||||
<a class="text-link px-2 py-1 underline" rel="next" href={page.url.next}>
|
||||
Next page
|
||||
</a>
|
||||
)
|
||||
|
|
|
@ -8,8 +8,19 @@ const renderer: RendererObject = {
|
|||
blockquote({ tokens }) {
|
||||
return `\n[quote]${this.parser.parseInline(tokens)}[/quote]\n`;
|
||||
},
|
||||
html() {
|
||||
throw new Error("Not supported by BBCode: html");
|
||||
html(token) {
|
||||
const { type, raw } = token;
|
||||
if (type === "html") {
|
||||
if (raw === "</a>") {
|
||||
return "[/url]";
|
||||
}
|
||||
const match = raw.match(/^<a(?:\w+[a-z-]+(?:="[^"]*"))*\w+href="([^"]+)"(?:\w+[a-z-]+(?:="[^"]*"))*\w*>$/);
|
||||
if (match&&match[1]) {
|
||||
return `[url=${match[1]}]`;
|
||||
}
|
||||
}
|
||||
console.error("ERROR: unhandled token markdownToBbcode.html", token);
|
||||
throw new Error("Unknown HTML token not supported by BBCode renderer");
|
||||
},
|
||||
heading({ tokens }) {
|
||||
return `\n${this.parser.parseInline(tokens)}\n`;
|
||||
|
@ -24,7 +35,8 @@ const renderer: RendererObject = {
|
|||
listitem({ tokens }) {
|
||||
return `[li]${this.parser.parseInline(tokens)}[/li]\n`;
|
||||
},
|
||||
checkbox() {
|
||||
checkbox(token) {
|
||||
console.error("ERROR: unhandled token markdownToBbcode.checkbox", token);
|
||||
throw new Error("Not supported by BBCode: checkbox");
|
||||
},
|
||||
paragraph({ tokens }) {
|
||||
|
|
|
@ -9,7 +9,7 @@ const renderer: RendererObject = {
|
|||
return `\n> ${this.parser.parseInline(tokens)}\n`;
|
||||
},
|
||||
html() {
|
||||
throw new Error("Not supported by plaintext: html");
|
||||
return "";
|
||||
},
|
||||
heading({ tokens }) {
|
||||
return `\n== ${this.parser.parseInline(tokens)} ==\n`;
|
||||
|
@ -23,8 +23,9 @@ const renderer: RendererObject = {
|
|||
listitem({ tokens }) {
|
||||
return `- ${this.parser.parseInline(tokens)}\n`;
|
||||
},
|
||||
checkbox() {
|
||||
throw new Error("Not supported by plaintext: checkbox");
|
||||
checkbox(token) {
|
||||
console.error("ERROR: unhandled token markdownToPlaintext.checkbox", token);
|
||||
throw new Error("Not supported by plaintext renderer: checkbox");
|
||||
},
|
||||
paragraph({ tokens }) {
|
||||
return `\n${this.parser.parseInline(tokens)}\n`;
|
||||
|
@ -52,14 +53,16 @@ const renderer: RendererObject = {
|
|||
br() {
|
||||
return "\n\n";
|
||||
},
|
||||
del() {
|
||||
throw new Error("Not supported by plaintext: del");
|
||||
del(token) {
|
||||
console.error("ERROR: unhandled token markdownToPlaintext.del", token);
|
||||
throw new Error("Not supported by plaintext renderer: del");
|
||||
},
|
||||
link({ tokens }) {
|
||||
return this.parser.parseInline(tokens);
|
||||
},
|
||||
image() {
|
||||
throw new Error("Not supported by plaintext: img");
|
||||
image(token) {
|
||||
console.error("ERROR: unhandled token markdownToPlaintext.image", token);
|
||||
throw new Error("Not supported by plaintext renderer: img");
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue