Better microformats support and add PUBLISH_DRAFTS envvar
This commit is contained in:
parent
132b2b69f3
commit
a335aff2d3
31 changed files with 269 additions and 153 deletions
.prettierignoreREADME.mdastro.config.mjspackage-lock.jsonpackage.json
public
src
components
content
config.ts
stories
layouts
pages
|
@ -1 +1,3 @@
|
||||||
src/components/DarkModeScript.astro
|
src/components/DarkModeScript.astro
|
||||||
|
.astro/
|
||||||
|
dist/
|
||||||
|
|
|
@ -28,11 +28,12 @@ npm run prettier # Prettier formatting
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
The following optional environment variables can be set with `.env`:
|
The following optional environment variables can be set within a `.env` file:
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|-|-|-|
|
| ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `APACHE_CONFIG` | boolean | Whether to generate an `.htaccess` Apache config file at the root of the output directory or not. |
|
| `APACHE_CONFIG` | boolean | If set to true, generates an `.htaccess` Apache config file at the root of the output directory. |
|
||||||
|
| `PUBLISH_DRAFTS` | boolean | If set to true, includes drafts in the production build. Published drafts still won't be directly indexed by any other pages. |
|
||||||
|
|
||||||
### Export story for upload
|
### Export story for upload
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ export default defineConfig({
|
||||||
env: {
|
env: {
|
||||||
schema: {
|
schema: {
|
||||||
APACHE_CONFIG: envField.boolean({ context: "server", access: "public", default: false }),
|
APACHE_CONFIG: envField.boolean({ context: "server", access: "public", default: false }),
|
||||||
|
PUBLISH_DRAFTS: envField.boolean({ context: "server", access: "public", default: false }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "gallery.badmanners.xyz",
|
"name": "gallery.badmanners.xyz",
|
||||||
"version": "1.7.4",
|
"version": "1.7.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gallery.badmanners.xyz",
|
"name": "gallery.badmanners.xyz",
|
||||||
"version": "1.7.4",
|
"version": "1.7.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.2",
|
"@astrojs/check": "^0.9.2",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gallery.badmanners.xyz",
|
"name": "gallery.badmanners.xyz",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.7.4",
|
"version": "1.7.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "astro sync",
|
"postinstall": "astro sync",
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
|
|
|
@ -2,10 +2,10 @@ The source code of this website is licensed under the MIT License: https://opens
|
||||||
|
|
||||||
The stories and games hosted on the website are copyrighted by me and licensed under CC-BY-NC-ND-4.0: https://creativecommons.org/licenses/by-nc-nd/4.0/
|
The stories and games hosted on the website are copyrighted by me and licensed under CC-BY-NC-ND-4.0: https://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||||
|
|
||||||
The briefcase logo and any unattributed characters are copyrighted and trademarked by me.
|
The briefcase logo, my characters, and any unattributed characters are copyrighted and trademarked by me, Bad Manners.
|
||||||
|
|
||||||
The Noto Sans and Noto Serif typefaces are copyrighted to the Noto Project Authors and distributed under the SIL Open Font License v1.1: https://opensource.org/license/ofl-1-1
|
The Noto Sans and Noto Serif typefaces are copyrighted to the Noto Project Authors and distributed under the SIL Open Font License v1.1: https://opensource.org/license/ofl-1-1
|
||||||
|
|
||||||
The generic SVG icons were created by Font Awesome and are distributed under CC-BY-4.0: https://creativecommons.org/licenses/by/4.0/
|
The generic SVG icons were created by Font Awesome and are distributed under CC-BY-4.0: https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
All third-party trademarks and attributed characters belong to their respective owners, and I'm not affiliated with any of them.
|
All third-party trademarks and attributed characters belong to their respective owners, and I (Bad Manners) am not affiliated with any of them.
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
document.querySelectorAll<HTMLElementTagNameMap["button"]>("button[data-dark-mode]").forEach((button) => {
|
document.querySelectorAll<HTMLElementTagNameMap["button"]>("button[data-dark-mode]").forEach((button) => {
|
||||||
button.classList.remove("hidden");
|
button.classList.remove("hidden");
|
||||||
button.removeAttribute("aria-hidden");
|
button.setAttribute("aria-hidden", "false");
|
||||||
button.addEventListener("click", (e) => {
|
button.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (colorScheme === "dark") {
|
if (colorScheme === "dark") {
|
||||||
|
|
|
@ -26,7 +26,7 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-stone-800 dark:text-stone-100" id="comments">
|
<div class="text-stone-800 dark:text-stone-100" id="comments">
|
||||||
<p class="my-1">
|
<p class="my-1">
|
||||||
<a class="text-link underline" href={link} target="_blank">View comments on Mastodon</a>
|
<a class="text-link underline" href={link} target="_blank">View comments on Mastodon</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -46,7 +46,7 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
class="-mt-1 mr-1 animate-spin"
|
class="-mt-1 mr-1 animate-spin"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<path
|
<path
|
||||||
|
@ -59,25 +59,32 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="template-comment-box">
|
<template id="template-comment-box">
|
||||||
<div class="my-2 rounded-md border-2 border-stone-400 bg-stone-200 p-2 dark:border-stone-600 dark:bg-stone-800">
|
<div
|
||||||
|
role="article"
|
||||||
|
class="p-comment h-entry my-2 rounded-md border-2 border-stone-400 bg-stone-100 p-2 dark:border-stone-600 dark:bg-stone-800"
|
||||||
|
>
|
||||||
<div class="ml-1">
|
<div class="ml-1">
|
||||||
<a data-author class="text-link flex items-center text-lg hover:underline focus:underline" target="_blank">
|
<a
|
||||||
<img data-avatar class="mr-2 w-10 rounded-full border border-stone-400 dark:border-stone-600" />
|
data-author
|
||||||
<span data-display-name></span>
|
class="p-author h-card u-url text-link flex items-center text-lg hover:underline focus:underline"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img data-avatar class="u-photo mr-2 w-10 rounded-full border border-stone-400 dark:border-stone-600" />
|
||||||
|
<span data-display-name class="p-nickname"></span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
data-post-link
|
data-post-link
|
||||||
class="text-link my-1 flex items-center text-sm font-light hover:underline focus:underline"
|
class="u-url text-link my-1 flex items-center text-sm font-light hover:underline focus:underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span class="mr-1" data-publish-date aria-label="Publish date"></span>
|
<time class="dt-published mr-1" data-publish-date aria-label="Publish date"></time>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div data-content class="prose-a:text-link prose-story prose my-1 dark:prose-invert prose-img:my-0"></div>
|
<div data-content class="e-content prose-a:text-link prose-story prose my-1 dark:prose-invert prose-img:my-0"></div>
|
||||||
<div class="ml-1 flex flex-row pb-2 pt-1">
|
<div class="ml-1 flex flex-row pb-2 pt-1">
|
||||||
<div class="flex" aria-label="Favorites">
|
<div class="flex" aria-label="Favorites">
|
||||||
<span data-favorites></span>
|
<span data-favorites></span>
|
||||||
<svg style={{ width: "1.25rem", fill: "currentColor" }} class="ml-2" viewBox="0 0 576 512" aria-hidden>
|
<svg style={{ width: "1.25rem", fill: "currentColor" }} class="ml-2" viewBox="0 0 576 512" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
|
d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
|
||||||
></path>
|
></path>
|
||||||
|
@ -85,14 +92,14 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4 flex" aria-label="Reblogs">
|
<div class="ml-4 flex" aria-label="Reblogs">
|
||||||
<span data-reblogs></span>
|
<span data-reblogs></span>
|
||||||
<svg style={{ width: "1.25rem", fill: "currentColor" }} class="ml-2" viewBox="0 0 512 512" aria-hidden>
|
<svg style={{ width: "1.25rem", fill: "currentColor" }} class="ml-2" viewBox="0 0 512 512" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
d="M0 224c0 17.7 14.3 32 32 32s32-14.3 32-32c0-53 43-96 96-96H320v32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S320 19.1 320 32V64H160C71.6 64 0 135.6 0 224zm512 64c0-17.7-14.3-32-32-32s-32 14.3-32 32c0 53-43 96-96 96H192V352c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V448H352c88.4 0 160-71.6 160-160z"
|
d="M0 224c0 17.7 14.3 32 32 32s32-14.3 32-32c0-53 43-96 96-96H320v32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S320 19.1 320 32V64H160C71.6 64 0 135.6 0 224zm512 64c0-17.7-14.3-32-32-32s-32 14.3-32 32c0 53-43 96-96 96H192V352c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V448H352c88.4 0 160-71.6 160-160z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-comment-thread class="-mb-2" aria-hidden></div>
|
<div data-comment-thread class="-mb-2" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -164,19 +171,21 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
)!;
|
)!;
|
||||||
data.descendants.forEach((comment) => {
|
data.descendants.forEach((comment) => {
|
||||||
const commentBox = commentTemplate.content.cloneNode(true) as HTMLDivElement;
|
const commentBox = commentTemplate.content.cloneNode(true) as HTMLDivElement;
|
||||||
|
commentBox.id = `comment-${comment.id}`;
|
||||||
|
|
||||||
const commentBoxAuthor = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-author]")!;
|
const commentBoxAuthor = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-author]")!;
|
||||||
commentBoxAuthor.href = comment.account.url;
|
commentBoxAuthor.href = comment.account.url;
|
||||||
const avatar = commentBoxAuthor.querySelector<HTMLElementTagNameMap["img"]>("img[data-avatar]")!;
|
const avatar = commentBoxAuthor.querySelector<HTMLElementTagNameMap["img"]>("img[data-avatar]")!;
|
||||||
avatar.src = comment.account.avatar;
|
avatar.src = comment.account.avatar;
|
||||||
avatar.alt = `Profile picture of ${comment.account.username}`;
|
avatar.alt = `Avatar of ${comment.account.username}`;
|
||||||
const displayName = commentBoxAuthor.querySelector<HTMLElementTagNameMap["span"]>("span[data-display-name]")!;
|
const displayName = commentBoxAuthor.querySelector<HTMLElementTagNameMap["span"]>("span[data-display-name]")!;
|
||||||
displayName.innerHTML = replaceEmojis(comment.account.display_name, comment.account.emojis);
|
displayName.innerHTML = replaceEmojis(comment.account.display_name, comment.account.emojis);
|
||||||
|
|
||||||
const commentBoxPostLink = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-post-link]")!;
|
const commentBoxPostLink = commentBox.querySelector<HTMLElementTagNameMap["a"]>("a[data-post-link]")!;
|
||||||
commentBoxPostLink.href = comment.url;
|
commentBoxPostLink.href = comment.url;
|
||||||
const publishDate =
|
const publishDate =
|
||||||
commentBoxPostLink.querySelector<HTMLElementTagNameMap["span"]>("span[data-publish-date]")!;
|
commentBoxPostLink.querySelector<HTMLElementTagNameMap["time"]>("time[data-publish-date]")!;
|
||||||
|
publishDate.setAttribute("datetime", comment.created_at);
|
||||||
publishDate.innerText = new Date(Date.parse(comment.created_at)).toLocaleString("en-US", {
|
publishDate.innerText = new Date(Date.parse(comment.created_at)).toLocaleString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
|
@ -186,8 +195,10 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (comment.edited_at) {
|
if (comment.edited_at) {
|
||||||
const edited = document.createElement("span");
|
const edited = document.createElement("time");
|
||||||
edited.className = "italic";
|
edited.className = "dt-updated italic";
|
||||||
|
edited.setAttribute("datetime", comment.edited_at);
|
||||||
|
edited.setAttribute("title", comment.edited_at);
|
||||||
edited.innerText = "(edited)";
|
edited.innerText = "(edited)";
|
||||||
commentBoxPostLink.appendChild(edited);
|
commentBoxPostLink.appendChild(edited);
|
||||||
}
|
}
|
||||||
|
@ -209,7 +220,7 @@ const { link, instance, user, postId } = Astro.props;
|
||||||
commentMap[comment.id] = commentsIndex;
|
commentMap[comment.id] = commentsIndex;
|
||||||
const parentThreadDiv =
|
const parentThreadDiv =
|
||||||
commentsList[commentsIndex].querySelector<HTMLElementTagNameMap["div"]>("div[data-comment-thread]")!;
|
commentsList[commentsIndex].querySelector<HTMLElementTagNameMap["div"]>("div[data-comment-thread]")!;
|
||||||
parentThreadDiv.removeAttribute("aria-hidden");
|
parentThreadDiv.setAttribute("aria-hidden", "false");
|
||||||
parentThreadDiv.setAttribute("aria-label", "Replies");
|
parentThreadDiv.setAttribute("aria-label", "Replies");
|
||||||
parentThreadDiv.appendChild(commentBox);
|
parentThreadDiv.appendChild(commentBox);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,20 @@ type Props = {
|
||||||
lang: Lang;
|
lang: Lang;
|
||||||
user: CollectionEntry<"users">;
|
user: CollectionEntry<"users">;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
rel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { user, lang, class: className } = Astro.props;
|
const { user, lang, class: className, rel } = Astro.props;
|
||||||
const username = getUsernameForLang(user, lang);
|
const username = getUsernameForLang(user, lang);
|
||||||
const link = user.data.preferredLink ? user.data.links[user.data.preferredLink]!.link : null;
|
const link = user.data.preferredLink ? user.data.links[user.data.preferredLink]!.link : null;
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
link ? (
|
link ? (
|
||||||
<a href={link} class:list={["h-card u-url text-link underline", className]} target="_blank">
|
<a rel={rel} href={link} class:list={[className, "h-card u-url text-link underline"]} target="_blank">
|
||||||
{username}
|
{username}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<span class:list={["h-card", className]}>{username}</span>
|
<span class:list={[className, "h-card"]}>{username}</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,8 +235,8 @@ const storiesCollection = defineCollection({
|
||||||
thumbnail: image().optional(),
|
thumbnail: image().optional(),
|
||||||
// Optional parameters
|
// Optional parameters
|
||||||
shortTitle: z.string().optional(),
|
shortTitle: z.string().optional(),
|
||||||
commissioner: userList.optional(),
|
commissioners: userList.optional(),
|
||||||
requester: userList.optional(),
|
requesters: userList.optional(),
|
||||||
summary: z.string().trim().optional(),
|
summary: z.string().trim().optional(),
|
||||||
thumbnailWidth: z.number().int().optional(),
|
thumbnailWidth: z.number().int().optional(),
|
||||||
thumbnailHeight: z.number().int().optional(),
|
thumbnailHeight: z.number().int().optional(),
|
||||||
|
|
|
@ -35,7 +35,7 @@ tags:
|
||||||
- hyper
|
- hyper
|
||||||
- netorare
|
- netorare
|
||||||
- commission
|
- commission
|
||||||
commissioner: scion
|
commissioners: scion
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
"": scion
|
"": scion
|
||||||
---
|
---
|
||||||
|
|
|
@ -34,7 +34,7 @@ tags:
|
||||||
- hyper
|
- hyper
|
||||||
- netorare
|
- netorare
|
||||||
- commission
|
- commission
|
||||||
commissioner: scion
|
commissioners: scion
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
"": scion
|
"": scion
|
||||||
---
|
---
|
||||||
|
|
|
@ -28,7 +28,7 @@ tags:
|
||||||
- size difference
|
- size difference
|
||||||
- implied perma endo
|
- implied perma endo
|
||||||
- request
|
- request
|
||||||
requester: dee-lumeni
|
requesters: dee-lumeni
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Kuronosuke: dee-lumeni
|
Kuronosuke: dee-lumeni
|
||||||
---
|
---
|
||||||
|
|
|
@ -37,7 +37,7 @@ tags:
|
||||||
- plushie
|
- plushie
|
||||||
- wardrobe malfunction
|
- wardrobe malfunction
|
||||||
- commission
|
- commission
|
||||||
commissioner: dee-lumeni
|
commissioners: dee-lumeni
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Rose: dee-lumeni
|
Rose: dee-lumeni
|
||||||
---
|
---
|
||||||
|
|
|
@ -34,7 +34,7 @@ tags:
|
||||||
- gay sex
|
- gay sex
|
||||||
- netorare
|
- netorare
|
||||||
- commission
|
- commission
|
||||||
commissioner: yolkmonkey
|
commissioners: yolkmonkey
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Yolk: yolkmonkey
|
Yolk: yolkmonkey
|
||||||
prev: team-effort
|
prev: team-effort
|
||||||
|
|
|
@ -32,7 +32,7 @@ tags:
|
||||||
- inflation
|
- inflation
|
||||||
- gay sex
|
- gay sex
|
||||||
- request
|
- request
|
||||||
requester: yolkmonkey
|
requesters: yolkmonkey
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Yolk: yolkmonkey
|
Yolk: yolkmonkey
|
||||||
next: team-building
|
next: team-building
|
||||||
|
|
|
@ -28,7 +28,7 @@ tags:
|
||||||
- same size
|
- same size
|
||||||
- long-term endo
|
- long-term endo
|
||||||
- request
|
- request
|
||||||
requester: avour-inden
|
requesters: avour-inden
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Avour: avour-inden
|
Avour: avour-inden
|
||||||
Buster: holi
|
Buster: holi
|
||||||
|
|
|
@ -40,7 +40,7 @@ tags:
|
||||||
- lesbian sex
|
- lesbian sex
|
||||||
- orgy
|
- orgy
|
||||||
- commission
|
- commission
|
||||||
commissioner: asof-yeun
|
commissioners: asof-yeun
|
||||||
copyrightedCharacters:
|
copyrightedCharacters:
|
||||||
Ushitora: asof-yeun
|
Ushitora: asof-yeun
|
||||||
---
|
---
|
||||||
|
|
|
@ -40,12 +40,26 @@ const currentYear = new Date().getFullYear().toString();
|
||||||
<span class="my-2 text-2xl font-semibold">Bad Manners</span>
|
<span class="my-2 text-2xl font-semibold">Bad Manners</span>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<div class="pt-4 text-center text-xs text-black dark:text-white">
|
<div class="pt-4 text-center text-xs text-black dark:text-white">
|
||||||
<span>© {currentYear == "2024" ? <time datetime="2024">2024</time> : <><time datetime="2024">2024</time>–<time datetime={currentYear}>{currentYear}</time></>} | </span>
|
<span
|
||||||
|
>© {
|
||||||
|
currentYear == "2024" ? (
|
||||||
|
<time datetime="2024">2024</time>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<time datetime="2024">2024</time>–<time datetime={currentYear}>{currentYear}</time>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} |
|
||||||
|
</span>
|
||||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex items-center gap-x-1 pb-10">
|
<div class="mt-2 flex items-center gap-x-1 pb-10">
|
||||||
<a class="text-link p-1" href="https://badmanners.xyz/" target="_blank" aria-labelled-by="label-main-website">
|
<a class="text-link p-1" href="https://badmanners.xyz/" target="_blank" aria-labelledby="label-main-website">
|
||||||
<svg style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }} viewBox="0 0 576 512" aria-hidden>
|
<svg
|
||||||
|
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
||||||
|
viewBox="0 0 576 512"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
|
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
|
||||||
></path>
|
></path>
|
||||||
|
@ -53,19 +67,23 @@ const currentYear = new Date().getFullYear().toString();
|
||||||
<span id="label-main-website" class="hidden">Main website</span>
|
<span id="label-main-website" class="hidden">Main website</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="text-link p-1" href="/feed.xml" target="_blank" aria-labelledby="label-rss-feed">
|
<a class="text-link p-1" href="/feed.xml" target="_blank" aria-labelledby="label-rss-feed">
|
||||||
<svg style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }} viewBox="0 0 448 512" aria-hidden>
|
<svg
|
||||||
|
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
||||||
|
viewBox="0 0 448 512"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span id="label-rss-feed" class="hidden">RSS feed</span>
|
<span id="label-rss-feed" class="hidden">RSS feed</span>
|
||||||
</a>
|
</a>
|
||||||
<button data-dark-mode class="text-link hidden p-1" aria-labelledby="label-toggle-dark-mode" aria-hidden>
|
<button data-dark-mode class="text-link hidden p-1" aria-labelledby="label-toggle-dark-mode" aria-hidden="true">
|
||||||
<svg
|
<svg
|
||||||
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
class="hidden dark:block"
|
class="hidden dark:block"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||||
|
@ -75,7 +93,7 @@ const currentYear = new Date().getFullYear().toString();
|
||||||
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
style={{ width: "1.5rem", height: "1.5rem", fill: "currentColor" }}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
class="block dark:hidden"
|
class="block dark:hidden"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||||
|
|
|
@ -54,7 +54,7 @@ const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !ga
|
||||||
/>
|
/>
|
||||||
<Fragment slot="section-information">
|
<Fragment slot="section-information">
|
||||||
<Authors lang={props.lang}>
|
<Authors lang={props.lang}>
|
||||||
{authorsList.map((author) => <UserComponent class="p-author" user={author} lang={props.lang} />)}
|
{authorsList.map((author) => <UserComponent rel="author" class="p-author" user={author} lang={props.lang} />)}
|
||||||
</Authors>
|
</Authors>
|
||||||
<div id="platforms">
|
<div id="platforms">
|
||||||
<p>{t(props.lang, "game/platforms", props.platforms)}</p>
|
<p>{t(props.lang, "game/platforms", props.platforms)}</p>
|
||||||
|
|
|
@ -115,9 +115,13 @@ const thumbnail =
|
||||||
<a
|
<a
|
||||||
href={series ? series.data.link : props.labelReturnTo.link}
|
href={series ? series.data.link : props.labelReturnTo.link}
|
||||||
class="text-link my-1 p-2"
|
class="text-link my-1 p-2"
|
||||||
aria-labelled-by="label-return-to"
|
aria-labelledby="label-return-to"
|
||||||
>
|
>
|
||||||
<svg style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }} viewBox="0 0 512 512" aria-hidden>
|
<svg
|
||||||
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
|
d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
|
||||||
></path>
|
></path>
|
||||||
|
@ -131,9 +135,13 @@ const thumbnail =
|
||||||
<a
|
<a
|
||||||
href="#description"
|
href="#description"
|
||||||
class="text-link my-1 border-l border-stone-300 p-2 dark:border-stone-700"
|
class="text-link my-1 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||||
aria-labelled-by="label-go-to-description"
|
aria-labelledby="label-go-to-description"
|
||||||
>
|
>
|
||||||
<svg style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }} viewBox="0 0 512 512" aria-hidden>
|
<svg
|
||||||
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
|
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
|
||||||
></path>
|
></path>
|
||||||
|
@ -144,14 +152,14 @@ const thumbnail =
|
||||||
<button
|
<button
|
||||||
data-dark-mode
|
data-dark-mode
|
||||||
class="text-link my-1 hidden border-l border-stone-300 p-2 dark:border-stone-700"
|
class="text-link my-1 hidden border-l border-stone-300 p-2 dark:border-stone-700"
|
||||||
aria-labelled-by="label-toggle-dark-mode"
|
aria-labelledby="label-toggle-dark-mode"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
class="hidden dark:block"
|
class="hidden dark:block"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||||
|
@ -161,7 +169,7 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
class="block dark:hidden"
|
class="block dark:hidden"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||||
|
@ -190,14 +198,14 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
viewBox="0 0 320 512"
|
viewBox="0 0 320 512"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{props.prev.title}</span>
|
<span>{props.prev.title}</span>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div class="h-full border-r border-stone-400 dark:border-stone-600" aria-hidden />
|
<div class="h-full border-r border-stone-400 dark:border-stone-600" aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
{props.next ? (
|
{props.next ? (
|
||||||
<a
|
<a
|
||||||
|
@ -210,13 +218,13 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
viewBox="0 0 320 512"
|
viewBox="0 0 320 512"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div aria-hidden />
|
<div aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
|
<hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
|
||||||
|
@ -277,7 +285,7 @@ const thumbnail =
|
||||||
<time
|
<time
|
||||||
id="publish-date"
|
id="publish-date"
|
||||||
datetime={props.pubDate.toISOString().slice(0, 10)}
|
datetime={props.pubDate.toISOString().slice(0, 10)}
|
||||||
class="dt-published block mt-2 px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
|
class="dt-published mt-2 block px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
|
||||||
aria-label={t(props.lang, "published_content/publish_date_aria_label")}
|
aria-label={t(props.lang, "published_content/publish_date_aria_label")}
|
||||||
aria-description={
|
aria-description={
|
||||||
t(props.lang, "published_content/publish_date_aria_description", props.pubDate) || undefined
|
t(props.lang, "published_content/publish_date_aria_description", props.pubDate) || undefined
|
||||||
|
@ -323,7 +331,7 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
viewBox="0 0 384 512"
|
viewBox="0 0 384 512"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
><path
|
><path
|
||||||
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
|
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
|
||||||
></path></svg
|
></path></svg
|
||||||
|
@ -345,7 +353,7 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
viewBox="0 0 320 512"
|
viewBox="0 0 320 512"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -365,7 +373,7 @@ const thumbnail =
|
||||||
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
style={{ width: "1.25rem", height: "1.25rem", fill: "currentColor" }}
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
viewBox="0 0 320 512"
|
viewBox="0 0 320 512"
|
||||||
aria-hidden
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -441,7 +449,9 @@ const thumbnail =
|
||||||
class="pt-6 text-center text-xs text-black dark:text-white"
|
class="pt-6 text-center text-xs text-black dark:text-white"
|
||||||
aria-label={t(props.lang, "published_content/copyright_aria_label")}
|
aria-label={t(props.lang, "published_content/copyright_aria_label")}
|
||||||
>
|
>
|
||||||
<span set:html={t(props.lang, "published_content/copyright_year", (props.pubDate || new Date()).getFullYear())}></span><span> |</span>
|
<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" target="_blank"
|
||||||
>{t(props.lang, "published_content/licenses")}</a
|
>{t(props.lang, "published_content/licenses")}</a
|
||||||
>
|
>
|
||||||
|
|
|
@ -15,8 +15,8 @@ const prev = props.prev && (await getEntry(props.prev));
|
||||||
const next = props.next && (await getEntry(props.next));
|
const next = props.next && (await getEntry(props.next));
|
||||||
const series = props.series && (await getEntry(props.series));
|
const series = props.series && (await getEntry(props.series));
|
||||||
const authorsList = await getEntries(props.authors);
|
const authorsList = await getEntries(props.authors);
|
||||||
const commissionersList = props.commissioner && (await getEntries(props.commissioner));
|
const commissionersList = props.commissioners && (await getEntries(props.commissioners));
|
||||||
const requestersList = props.requester && (await getEntries(props.requester));
|
const requestersList = props.requesters && (await getEntries(props.requesters));
|
||||||
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||||
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||||
const wordCount = props.wordCount?.toString();
|
const wordCount = props.wordCount?.toString();
|
||||||
|
@ -65,7 +65,7 @@ const wordCount = props.wordCount?.toString();
|
||||||
/>
|
/>
|
||||||
<Fragment slot="section-information">
|
<Fragment slot="section-information">
|
||||||
<Authors lang={props.lang}>
|
<Authors lang={props.lang}>
|
||||||
{authorsList.map((author) => <UserComponent class="p-author" user={author} lang={props.lang} />)}
|
{authorsList.map((author) => <UserComponent rel="author" class="p-author" user={author} lang={props.lang} />)}
|
||||||
</Authors>
|
</Authors>
|
||||||
{
|
{
|
||||||
requestersList && (
|
requestersList && (
|
||||||
|
|
|
@ -47,8 +47,8 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
||||||
const { lang } = story.data;
|
const { lang } = story.data;
|
||||||
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
|
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
|
||||||
const authorsList = await getEntries(story.data.authors);
|
const authorsList = await getEntries(story.data.authors);
|
||||||
const commissionersList = story.data.commissioner && (await getEntries(story.data.commissioner));
|
const commissionersList = story.data.commissioners && (await getEntries(story.data.commissioners));
|
||||||
const requestersList = story.data.requester && (await getEntries(story.data.requester));
|
const requestersList = story.data.requesters && (await getEntries(story.data.requesters));
|
||||||
|
|
||||||
const description = await Promise.all(
|
const description = await Promise.all(
|
||||||
WEBSITE_LIST.map(async ({ website, exportFormat }) => {
|
WEBSITE_LIST.map(async ({ website, exportFormat }) => {
|
||||||
|
|
|
@ -52,18 +52,18 @@ async function storyFeedItem(
|
||||||
"story/authors",
|
"story/authors",
|
||||||
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
|
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
|
||||||
)}</p>` +
|
)}</p>` +
|
||||||
(data.requester
|
(data.requesters
|
||||||
? `<p>${t(
|
? `<p>${t(
|
||||||
data.lang,
|
data.lang,
|
||||||
"story/requested_by",
|
"story/requested_by",
|
||||||
(await getEntries(data.requester)).map((requester) => getLinkForUser(requester, data.lang)),
|
(await getEntries(data.requesters)).map((requester) => getLinkForUser(requester, data.lang)),
|
||||||
)}</p>`
|
)}</p>`
|
||||||
: "") +
|
: "") +
|
||||||
(data.commissioner
|
(data.commissioners
|
||||||
? `<p>${t(
|
? `<p>${t(
|
||||||
data.lang,
|
data.lang,
|
||||||
"story/commissioned_by",
|
"story/commissioned_by",
|
||||||
(await getEntries(data.commissioner)).map((commissioner) => getLinkForUser(commissioner, data.lang)),
|
(await getEntries(data.commissioners)).map((commissioner) => getLinkForUser(commissioner, data.lang)),
|
||||||
)}</p>`
|
)}</p>`
|
||||||
: "") +
|
: "") +
|
||||||
`<hr><p><em>${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}</em></p>` +
|
`<hr><p><em>${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}</em></p>` +
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
---
|
---
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import { getCollection, type CollectionEntry } from "astro:content";
|
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
|
||||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||||
import { t } from "../i18n";
|
import { DEFAULT_LANG, t } from "../i18n";
|
||||||
|
import UserComponent from "../components/UserComponent.astro";
|
||||||
|
|
||||||
type GameWithPubDate = CollectionEntry<"games"> & { data: { pubDate: Date } };
|
type GameWithPubDate = CollectionEntry<"games"> & { data: { pubDate: Date } };
|
||||||
|
|
||||||
const games = (
|
const games = await Promise.all(
|
||||||
(await getCollection("games", (game) => !game.data.isDraft && game.data.pubDate)) as GameWithPubDate[]
|
((await getCollection("games", (game) => !game.data.isDraft && game.data.pubDate)) as GameWithPubDate[])
|
||||||
).sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||||
|
.map(async (game) => ({
|
||||||
|
...game,
|
||||||
|
authors: await getEntries(game.data.authors),
|
||||||
|
})),
|
||||||
|
);
|
||||||
---
|
---
|
||||||
|
|
||||||
<GalleryLayout pageTitle="Games" class="h-feed">
|
<GalleryLayout pageTitle="Games" class="h-feed">
|
||||||
|
@ -17,7 +23,7 @@ const games = (
|
||||||
<p class="p-summary my-4">A game that I've gone and done.</p>
|
<p class="p-summary my-4">A game that I've gone and done.</p>
|
||||||
<ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
<ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
||||||
{
|
{
|
||||||
games.map((game) => (
|
games.map((game, i) => (
|
||||||
<li class="h-entry">
|
<li class="h-entry">
|
||||||
<a
|
<a
|
||||||
class="u-url text-link hover:underline focus:underline"
|
class="u-url text-link hover:underline focus:underline"
|
||||||
|
@ -26,7 +32,13 @@ const games = (
|
||||||
>
|
>
|
||||||
{game.data.thumbnail ? (
|
{game.data.thumbnail ? (
|
||||||
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
||||||
<Image class="u-photo m-auto" src={game.data.thumbnail} alt={`Thumbnail for ${game.data.title}`} width={288} />
|
<Image
|
||||||
|
loading={i < 10 ? "eager" : "lazy"}
|
||||||
|
class="u-photo m-auto"
|
||||||
|
src={game.data.thumbnail}
|
||||||
|
alt={`Thumbnail for ${game.data.title}`}
|
||||||
|
width={288}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div class="max-w-[288px] text-sm">
|
<div class="max-w-[288px] text-sm">
|
||||||
|
@ -37,6 +49,11 @@ const games = (
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<div style={{ display: "none" }}>
|
||||||
|
{game.authors.map((author) => (
|
||||||
|
<UserComponent rel="author" class="p-author" user={author} lang={DEFAULT_LANG} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import type { GetStaticPaths } from "astro";
|
import type { GetStaticPaths } from "astro";
|
||||||
import { type CollectionEntry, getCollection } from "astro:content";
|
import { type CollectionEntry, getCollection } from "astro:content";
|
||||||
import GameLayout from "../../layouts/GameLayout.astro";
|
import GameLayout from "../../layouts/GameLayout.astro";
|
||||||
|
import { PUBLISH_DRAFTS } from "astro:env/server";
|
||||||
|
|
||||||
type Props = CollectionEntry<"games">;
|
type Props = CollectionEntry<"games">;
|
||||||
|
|
||||||
|
@ -11,10 +12,12 @@ type Params = {
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const games = await getCollection("games");
|
const games = await getCollection("games");
|
||||||
return games.map((game) => ({
|
return games
|
||||||
params: { slug: game.slug } satisfies Params,
|
.filter((game) => import.meta.env.DEV || PUBLISH_DRAFTS || !game.data.isDraft)
|
||||||
props: game satisfies Props,
|
.map((game) => ({
|
||||||
}));
|
params: { slug: game.slug } satisfies Params,
|
||||||
|
props: game satisfies Props,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const game = Astro.props;
|
const game = Astro.props;
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
---
|
---
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
import { type CollectionEntry, type CollectionKey, getCollection } from "astro:content";
|
import { type CollectionEntry, type CollectionKey, getCollection, getEntries } from "astro:content";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||||
import { t } from "../i18n";
|
import { DEFAULT_LANG, t, type Lang } from "../i18n";
|
||||||
|
import UserComponent from "../components/UserComponent.astro";
|
||||||
|
|
||||||
const MAX_ITEMS = 8;
|
const MAX_ITEMS = 10;
|
||||||
|
|
||||||
interface LatestItemsEntry {
|
interface LatestItemsEntry {
|
||||||
type: string;
|
type: string;
|
||||||
thumbnail?: ImageMetadata;
|
thumbnail?: ImageMetadata;
|
||||||
href: string;
|
href: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
lang: Lang;
|
||||||
|
authors: CollectionEntry<"users">[];
|
||||||
altText: string;
|
altText: string;
|
||||||
pubDate: Date;
|
pubDate: Date;
|
||||||
}
|
}
|
||||||
|
@ -32,27 +35,42 @@ const games = (
|
||||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||||
.slice(0, MAX_ITEMS);
|
.slice(0, MAX_ITEMS);
|
||||||
|
|
||||||
const latestItems: LatestItemsEntry[] = [
|
const latestItems: LatestItemsEntry[] = await Promise.all(
|
||||||
stories.map<LatestItemsEntry>((story) => ({
|
[
|
||||||
type: "Story",
|
stories.map((story) => ({
|
||||||
thumbnail: story.data.thumbnail,
|
date: story.data.pubDate,
|
||||||
href: `/stories/${story.slug}`,
|
fn: async () =>
|
||||||
title: story.data.title,
|
({
|
||||||
altText: t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning),
|
type: "Story",
|
||||||
pubDate: story.data.pubDate,
|
thumbnail: story.data.thumbnail,
|
||||||
})),
|
href: `/stories/${story.slug}`,
|
||||||
games.map<LatestItemsEntry>((game) => ({
|
title: story.data.title,
|
||||||
type: "Game",
|
authors: await getEntries(story.data.authors),
|
||||||
thumbnail: game.data.thumbnail,
|
lang: story.data.lang,
|
||||||
href: `/games/${game.slug}`,
|
altText: t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning),
|
||||||
title: game.data.title,
|
pubDate: story.data.pubDate,
|
||||||
altText: t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning),
|
}) satisfies LatestItemsEntry,
|
||||||
pubDate: game.data.pubDate,
|
})),
|
||||||
})),
|
games.map((game) => ({
|
||||||
]
|
date: game.data.pubDate,
|
||||||
.flat()
|
fn: async () =>
|
||||||
.sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime())
|
({
|
||||||
.slice(0, MAX_ITEMS);
|
type: "Game",
|
||||||
|
thumbnail: game.data.thumbnail,
|
||||||
|
href: `/games/${game.slug}`,
|
||||||
|
title: game.data.title,
|
||||||
|
authors: await getEntries(game.data.authors),
|
||||||
|
lang: DEFAULT_LANG,
|
||||||
|
altText: t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning),
|
||||||
|
pubDate: game.data.pubDate,
|
||||||
|
}) satisfies LatestItemsEntry,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
.flat()
|
||||||
|
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
.slice(0, MAX_ITEMS)
|
||||||
|
.map(async (entry) => await entry.fn()),
|
||||||
|
);
|
||||||
---
|
---
|
||||||
|
|
||||||
<GalleryLayout pageTitle="Gallery" class="h-feed">
|
<GalleryLayout pageTitle="Gallery" class="h-feed">
|
||||||
|
@ -60,8 +78,8 @@ const latestItems: LatestItemsEntry[] = [
|
||||||
<h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">My gallery</h1>
|
<h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">My gallery</h1>
|
||||||
<div class="p-summary">
|
<div class="p-summary">
|
||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
Welcome to my gallery! You can expect lots of safe vore/endosoma ahead. Use the navigation menu to navigate through
|
Welcome to my gallery! You can expect lots of safe vore/endosoma ahead. Use the navigation menu to navigate
|
||||||
my content.
|
through my content.
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-8">
|
<ul class="list-disc pl-8">
|
||||||
<li><a class="text-link underline" href="/stories/1">Read my stories!</a></li>
|
<li><a class="text-link underline" href="/stories/1">Read my stories!</a></li>
|
||||||
|
@ -85,7 +103,13 @@ const latestItems: LatestItemsEntry[] = [
|
||||||
<a class="u-url text-link hover:underline focus:underline" href={entry.href} title={entry.altText}>
|
<a class="u-url text-link hover:underline focus:underline" href={entry.href} title={entry.altText}>
|
||||||
{entry.thumbnail ? (
|
{entry.thumbnail ? (
|
||||||
<div class="flex aspect-square max-w-[192px] justify-center">
|
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||||
<Image class="u-photo m-auto" src={entry.thumbnail} alt={`Thumbnail for ${entry.title}`} width={192} />
|
<Image
|
||||||
|
loading="eager"
|
||||||
|
class="u-photo m-auto"
|
||||||
|
src={entry.thumbnail}
|
||||||
|
alt={`Thumbnail for ${entry.title}`}
|
||||||
|
width={192}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div class="max-w-[192px] text-sm">
|
<div class="max-w-[192px] text-sm">
|
||||||
|
@ -93,10 +117,17 @@ const latestItems: LatestItemsEntry[] = [
|
||||||
<br />
|
<br />
|
||||||
<span class="italic">
|
<span class="italic">
|
||||||
<span class="p-category">{entry.type}</span> –{" "}
|
<span class="p-category">{entry.type}</span> –{" "}
|
||||||
<time class="dt-published" datetime={entry.pubDate.toISOString().slice(0, 10)}>{entry.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}</time>
|
<time class="dt-published" datetime={entry.pubDate.toISOString().slice(0, 10)}>
|
||||||
|
{entry.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
|
||||||
|
</time>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<div style={{ display: "none" }}>
|
||||||
|
{entry.authors.map((author) => (
|
||||||
|
<UserComponent rel="author" class="p-author" user={author} lang={entry.lang} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { GetStaticPaths } from "astro";
|
||||||
import { type CollectionEntry, getCollection } from "astro:content";
|
import { type CollectionEntry, getCollection } from "astro:content";
|
||||||
import getReadingTime from "reading-time";
|
import getReadingTime from "reading-time";
|
||||||
import StoryLayout from "../../layouts/StoryLayout.astro";
|
import StoryLayout from "../../layouts/StoryLayout.astro";
|
||||||
|
import { PUBLISH_DRAFTS } from "astro:env/server";
|
||||||
|
|
||||||
type Props = CollectionEntry<"stories">;
|
type Props = CollectionEntry<"stories">;
|
||||||
|
|
||||||
|
@ -12,10 +13,12 @@ type Params = {
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const stories = await getCollection("stories");
|
const stories = await getCollection("stories");
|
||||||
return stories.map((story) => ({
|
return stories
|
||||||
params: { slug: story.slug } satisfies Params,
|
.filter((story) => import.meta.env.DEV || PUBLISH_DRAFTS || !story.data.isDraft)
|
||||||
props: story satisfies Props,
|
.map((story) => ({
|
||||||
}));
|
params: { slug: story.slug } satisfies Params,
|
||||||
|
props: story satisfies Props,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const story = Astro.props;
|
const story = Astro.props;
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
---
|
---
|
||||||
import type { GetStaticPaths, Page } from "astro";
|
import type { GetStaticPaths, Page } from "astro";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import { getCollection, type CollectionEntry } from "astro:content";
|
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
|
||||||
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
|
import UserComponent from "../../components/UserComponent.astro";
|
||||||
|
|
||||||
type StoryWithPubDate = CollectionEntry<"stories"> & { data: { pubDate: Date } };
|
type StoryWithPubDate = CollectionEntry<"stories"> & { data: { pubDate: Date } };
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
page: Page<StoryWithPubDate>;
|
page: Page<StoryWithPubDate & { authors: CollectionEntry<"users">[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
|
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
|
||||||
const stories = (
|
const stories = await Promise.all(
|
||||||
(await getCollection("stories", (story) => !story.data.isDraft && story.data.pubDate)) as StoryWithPubDate[]
|
((await getCollection("stories", (story) => !story.data.isDraft && story.data.pubDate)) as StoryWithPubDate[])
|
||||||
).sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
|
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||||
|
.map(async (story) => ({
|
||||||
|
...story,
|
||||||
|
authors: await getEntries(story.data.authors),
|
||||||
|
})),
|
||||||
|
);
|
||||||
return paginate(stories, { pageSize: 30 }) satisfies { props: Props }[];
|
return paginate(stories, { pageSize: 30 }) satisfies { props: Props }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,20 +50,22 @@ const totalPages = Math.ceil(page.total / page.size);
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
[...Array(totalPages).keys()].map((p) =>
|
[...Array(totalPages).keys()]
|
||||||
p + 1 == page.currentPage ? (
|
.map((p) => p + 1)
|
||||||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
.map((p) =>
|
||||||
{p + 1}
|
p == page.currentPage ? (
|
||||||
</span>
|
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||||||
) : (
|
{p}
|
||||||
<a
|
</span>
|
||||||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
) : (
|
||||||
href={page.url.current.replace(`/stories/${page.currentPage}`, `/stories/${p + 1}`)}
|
<a
|
||||||
>
|
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||||||
{p + 1}
|
href={page.url.current.replace(`/stories/${page.currentPage}`, `/stories/${p}`)}
|
||||||
</a>
|
>
|
||||||
),
|
{p}
|
||||||
)
|
</a>
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
page.url.next && (
|
page.url.next && (
|
||||||
|
@ -69,7 +77,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
||||||
</div>
|
</div>
|
||||||
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
||||||
{
|
{
|
||||||
page.data.map((story) => (
|
page.data.map((story, i) => (
|
||||||
<li class="h-entry break-inside-avoid">
|
<li class="h-entry break-inside-avoid">
|
||||||
<a
|
<a
|
||||||
class="u-url text-link hover:underline focus:underline"
|
class="u-url text-link hover:underline focus:underline"
|
||||||
|
@ -79,6 +87,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
||||||
{story.data.thumbnail ? (
|
{story.data.thumbnail ? (
|
||||||
<div class="flex aspect-square max-w-[192px] justify-center">
|
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||||
<Image
|
<Image
|
||||||
|
loading={i < 10 ? "eager" : "lazy"}
|
||||||
class="u-photo m-auto"
|
class="u-photo m-auto"
|
||||||
src={story.data.thumbnail}
|
src={story.data.thumbnail}
|
||||||
alt={`Thumbnail for ${story.data.title}`}
|
alt={`Thumbnail for ${story.data.title}`}
|
||||||
|
@ -94,6 +103,11 @@ const totalPages = Math.ceil(page.total / page.size);
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<div style={{ display: "none" }}>
|
||||||
|
{story.authors.map((author) => (
|
||||||
|
<UserComponent rel="author" class="p-author" user={author} lang={story.data.lang} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -107,20 +121,22 @@ const totalPages = Math.ceil(page.total / page.size);
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
[...Array(totalPages).keys()].map((p) =>
|
[...Array(totalPages).keys()]
|
||||||
p + 1 == page.currentPage ? (
|
.map((p) => p + 1)
|
||||||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
.map((p) =>
|
||||||
{p + 1}
|
p == page.currentPage ? (
|
||||||
</span>
|
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||||||
) : (
|
{p}
|
||||||
<a
|
</span>
|
||||||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
) : (
|
||||||
href={page.url.current.replace(`/stories/${page.currentPage}`, `/stories/${p + 1}`)}
|
<a
|
||||||
>
|
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||||||
{p + 1}
|
href={page.url.current.replace(`/stories/${page.currentPage}`, `/stories/${p}`)}
|
||||||
</a>
|
>
|
||||||
),
|
{p}
|
||||||
)
|
</a>
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
page.url.next && (
|
page.url.next && (
|
||||||
|
|
|
@ -27,7 +27,9 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
|
||||||
content="The Lost of the Marshes || The story of Quince, Nikili, and Suu."
|
content="The Lost of the Marshes || The story of Quince, Nikili, and Suu."
|
||||||
/>
|
/>
|
||||||
<h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">{series.data.name}</h1>
|
<h1 class="p-name m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">{series.data.name}</h1>
|
||||||
<p class="p-summary my-4">This is the main hub for the story of Quince, Nikili, and Suu, as well as all bonus content.</p>
|
<p class="p-summary my-4">
|
||||||
|
This is the main hub for the story of Quince, Nikili, and Suu, as well as all bonus content.
|
||||||
|
</p>
|
||||||
<section class="my-2" aria-labelledby="main-chapters">
|
<section class="my-2" aria-labelledby="main-chapters">
|
||||||
<h2
|
<h2
|
||||||
id="main-chapters"
|
id="main-chapters"
|
||||||
|
|
|
@ -155,7 +155,7 @@ const totalWorksWithTag = t(
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})}
|
})}
|
||||||
</span>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue