Improvements to types and age verification screen
This commit is contained in:
parent
18e98cdb3b
commit
7f7a62a391
78 changed files with 1132 additions and 1102 deletions
|
|
@ -1,55 +0,0 @@
|
|||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle="Age verification">
|
||||
<div class="bg-stone-100 dark:bg-stone-900">
|
||||
<div class="mx-auto flex min-h-screen max-w-3xl flex-col items-center justify-center text-center tracking-tight">
|
||||
<div class="h-14 w-14 text-bm-500 sm:h-16 sm:w-16 dark:text-bm-400">
|
||||
<svg class="fill-current" viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="pb-3 pt-2 text-2xl font-light text-stone-700 sm:pb-4 sm:pt-2 sm:text-3xl dark:text-stone-50">
|
||||
Age verification
|
||||
</div>
|
||||
<div class="w-full max-w-xl">
|
||||
<div
|
||||
class="mx-6 mb-4 border-b border-stone-300 pb-4 text-base text-stone-700 sm:text-xl dark:border-stone-300 dark:text-stone-50"
|
||||
>
|
||||
You must be 18+ to access this page.
|
||||
</div>
|
||||
</div>
|
||||
<p class="px-8 text-base font-light leading-snug text-stone-700 sm:max-w-2xl sm:text-lg dark:text-stone-50">
|
||||
By confirming that you are at least 18 years old, your selection will be saved to your browser to prevent this
|
||||
screen from appearing in the future.
|
||||
</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
|
||||
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"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
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"
|
||||
>
|
||||
I'm at least 18 years old
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script is:inline>
|
||||
document.querySelector("#age-verification-reject").addEventListener("click", () => {
|
||||
window.location.href = "about:blank";
|
||||
});
|
||||
document.querySelector("#age-verification-accept").addEventListener("click", () => {
|
||||
localStorage.setItem("ageVerified", "true");
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
window.location.href = decodeURIComponent(params.get("redirect") || "/");
|
||||
});
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
|
@ -108,70 +108,67 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
|
|||
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
|
||||
}
|
||||
|
||||
function isPreferredWebsite(
|
||||
user: CollectionEntry<"users">,
|
||||
website: Website,
|
||||
preferredChoices: readonly Website[],
|
||||
): boolean {
|
||||
const { preferredLink, links } = user.data;
|
||||
if (!(website in links)) {
|
||||
return false;
|
||||
}
|
||||
if (!preferredLink || preferredLink == website) {
|
||||
return true;
|
||||
}
|
||||
return !preferredChoices.includes(preferredLink);
|
||||
}
|
||||
|
||||
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite): string {
|
||||
if (user.data.isAnonymous) {
|
||||
return "anonymous";
|
||||
}
|
||||
switch (website) {
|
||||
case "eka":
|
||||
if (user.data.links.eka) {
|
||||
if ("eka" in user.data.links) {
|
||||
return `:icon${getUsernameForWebsite(user, "eka")}:`;
|
||||
}
|
||||
break;
|
||||
case "furaffinity":
|
||||
if (user.data.links.furaffinity) {
|
||||
if ("furaffinity" in user.data.links) {
|
||||
return `:icon${getUsernameForWebsite(user, "furaffinity")}:`;
|
||||
}
|
||||
break;
|
||||
case "weasyl":
|
||||
if (user.data.links.weasyl) {
|
||||
const weasylPreferredWebsites = ["furaffinity", "inkbunny", "sofurry"] as const;
|
||||
if ("weasyl" in user.data.links) {
|
||||
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
|
||||
} else if (
|
||||
user.data.links.furaffinity &&
|
||||
!(["inkbunny", "sofurry"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity", weasylPreferredWebsites)) {
|
||||
return `<fa:${getUsernameForWebsite(user, "furaffinity")}>`;
|
||||
} else if (
|
||||
user.data.links.inkbunny &&
|
||||
!(["furaffinity", "sofurry"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "inkbunny", weasylPreferredWebsites)) {
|
||||
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
|
||||
} else if (
|
||||
user.data.links.sofurry &&
|
||||
!(["furaffinity", "inkbunny"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "sofurry", weasylPreferredWebsites)) {
|
||||
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
|
||||
}
|
||||
break;
|
||||
case "inkbunny":
|
||||
if (user.data.links.inkbunny) {
|
||||
const inkbunnyPreferredWebsites = ["furaffinity", "sofurry", "weasyl"] as const;
|
||||
if ("inkbunny" in user.data.links) {
|
||||
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
|
||||
} else if (
|
||||
user.data.links.furaffinity &&
|
||||
!(["sofurry", "weasyl"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity", inkbunnyPreferredWebsites)) {
|
||||
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
|
||||
} else if (
|
||||
user.data.links.sofurry &&
|
||||
!(["furaffinity", "weasyl"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "sofurry", inkbunnyPreferredWebsites)) {
|
||||
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
|
||||
} else if (
|
||||
user.data.links.weasyl &&
|
||||
!(["furaffinity", "sofurry"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "weasyl", inkbunnyPreferredWebsites)) {
|
||||
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
|
||||
}
|
||||
break;
|
||||
case "sofurry":
|
||||
if (user.data.links.sofurry) {
|
||||
const sofurryPreferredWebsites = ["furaffinity", "inkbunny"] as const;
|
||||
if ("sofurry" in user.data.links) {
|
||||
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
|
||||
} else if (
|
||||
user.data.links.furaffinity &&
|
||||
!(["inkbunny"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity", sofurryPreferredWebsites)) {
|
||||
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
|
||||
} else if (
|
||||
user.data.links.inkbunny &&
|
||||
!(["furaffinity"] satisfies Website[] as (Website | null)[]).includes(user.data.preferredLink)
|
||||
) {
|
||||
} else if (isPreferredWebsite(user, "inkbunny", sofurryPreferredWebsites)) {
|
||||
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
|
||||
}
|
||||
break;
|
||||
|
|
@ -186,7 +183,7 @@ function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite):
|
|||
throw new Error(`No preferredLink "${user.data.preferredLink}" for user ${user.id}`);
|
||||
}
|
||||
}
|
||||
throw new Error(`No "${website}"-supported link for user "${user.id}" without preferredLink`);
|
||||
throw new Error(`No "${website}" link for user "${user.id}" (consider setting preferredLink)`);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
|
|
@ -268,12 +265,15 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, params: {
|
|||
(_, group1, group2) => `[${group1}](${new URL(group2, new URL(`/stories/${story.slug}`, site)).toString()})`,
|
||||
);
|
||||
const headers = { "Content-Type": "text/markdown; charset=utf-8" };
|
||||
const bbcodeExports: ReadonlyArray<ExportWebsite> = ["eka", "furaffinity", "inkbunny", "sofurry"] as const;
|
||||
const markdownExport: ReadonlyArray<ExportWebsite> = ["weasyl"] as const;
|
||||
// BBCode exports
|
||||
if ((["eka", "furaffinity", "inkbunny", "sofurry"] satisfies ExportWebsite[] as ExportWebsite[]).includes(website)) {
|
||||
if (bbcodeExports.includes(website)) {
|
||||
storyDescription = he.decode(await marked.use({ renderer: bbcodeRenderer }).parse(storyDescription));
|
||||
headers["Content-Type"] = "text/plain; charset=utf-8";
|
||||
// Markdown exports (no-op)
|
||||
} else if (!(["weasyl"] satisfies ExportWebsite[] as ExportWebsite[]).includes(website)) {
|
||||
} else if (!markdownExport.includes(website)) {
|
||||
console.log(`Unrecognized ExportWebsite "${website}"`);
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
return new Response(`${storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()}\n`, { headers });
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import { getCollection, getEntry } from "astro:content";
|
||||
import { Image } from "astro:assets";
|
||||
import { getUnixTime } from "date-fns";
|
||||
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
||||
import mapImage from "../../assets/images/tlotm_map.jpg";
|
||||
|
||||
const stories = await getCollection(
|
||||
"stories",
|
||||
(story) => !story.data.isDraft && story.slug.startsWith("the-lost-of-the-marshes/"),
|
||||
);
|
||||
const series = await getEntry("series", "the-lost-of-the-marshes");
|
||||
const stories = await getCollection("stories", (story) => !story.data.isDraft && story.data.series?.id === series.id);
|
||||
const mainChapters = stories
|
||||
.filter((story) => story.slug.startsWith("the-lost-of-the-marshes/chapter-"))
|
||||
.sort((a, b) => getUnixTime(a.data.pubDate) - getUnixTime(b.data.pubDate));
|
||||
|
|
@ -18,8 +16,8 @@ const bonusChapters = stories
|
|||
const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summary);
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="The Lost of the Marshes">
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">The Lost of the Marshes</h1>
|
||||
<GalleryLayout pageTitle={series.data.name}>
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">{series.data.name}</h1>
|
||||
<p class="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">
|
||||
<h2 id="main-chapters" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Main chapters</h2>
|
||||
|
|
|
|||
|
|
@ -10,83 +10,73 @@ const [stories, games, tagCategories] = await Promise.all([
|
|||
]);
|
||||
const tagsSet = new Set<string>();
|
||||
const draftOnlyTagsSet = new Set<string>();
|
||||
const seriesList: Record<string, string> = {};
|
||||
stories.filter((story) => !story.data.isDraft).forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
const seriesCollection = await getCollection("series");
|
||||
stories
|
||||
.filter((story) => !story.data.isDraft)
|
||||
.forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
});
|
||||
});
|
||||
if (story.data.series) {
|
||||
const [series, url] = Object.entries(story.data.series)[0];
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(
|
||||
`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`,
|
||||
);
|
||||
games
|
||||
.filter((game) => !game.data.isDraft)
|
||||
.forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
});
|
||||
});
|
||||
stories
|
||||
.filter((story) => story.data.isDraft)
|
||||
.forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
if (!tagsSet.has(tag)) {
|
||||
draftOnlyTagsSet.add(tag);
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
games.filter((game) => !game.data.isDraft).forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
});
|
||||
});
|
||||
if (game.data.series) {
|
||||
const [series, url] = Object.entries(game.data.series)[0];
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(
|
||||
`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`,
|
||||
);
|
||||
games
|
||||
.filter((game) => game.data.isDraft)
|
||||
.forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
if (!tagsSet.has(tag)) {
|
||||
draftOnlyTagsSet.add(tag);
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
stories.filter((story) => story.data.isDraft).forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
if (!tagsSet.has(tag)) {
|
||||
draftOnlyTagsSet.add(tag);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
games.filter((game) => game.data.isDraft).forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
if (!tagsSet.has(tag)) {
|
||||
draftOnlyTagsSet.add(tag);
|
||||
}
|
||||
});
|
||||
});
|
||||
const uncategorizedTagsSet = new Set(tagsSet);
|
||||
|
||||
const categorizedTags: Array<[string, string, string[]]> = tagCategories.sort((a, b) => a.data.index - b.data.index).map(category => {
|
||||
const tagList = category.data.tags.map(tag => {
|
||||
if (typeof tag === "string") {
|
||||
return tag
|
||||
}
|
||||
if (!("eng" in tag)) {
|
||||
throw new Error(`"{[lang]: text}" tag must have an "eng" key: ${tag}`)
|
||||
}
|
||||
return tag["eng"]!
|
||||
})
|
||||
tagList.forEach((tag, index) => {
|
||||
if (index !== tagList.findLastIndex((val) => tag == val)) {
|
||||
throw new Error(`Duplicated tag "${tag}" found in multiple tag-categories`)
|
||||
}
|
||||
})
|
||||
return [category.data.name, category.id, tagList.filter(tag => {
|
||||
if (draftOnlyTagsSet.has(tag)) {
|
||||
console.log(`Omitting draft-only tag "${tag}"`);
|
||||
return false;
|
||||
}
|
||||
if (tagsSet.has(tag)) {
|
||||
uncategorizedTagsSet.delete(tag);
|
||||
}
|
||||
return true;
|
||||
})];
|
||||
})
|
||||
const categorizedTags: Array<[string, string, string[]]> = tagCategories
|
||||
.sort((a, b) => a.data.index - b.data.index)
|
||||
.map((category) => {
|
||||
const tagList = category.data.tags.map((tag) => {
|
||||
if (typeof tag === "string") {
|
||||
return tag;
|
||||
}
|
||||
if (!("eng" in tag)) {
|
||||
throw new Error(`"{[lang]: text}" tag must have an "eng" key: ${tag}`);
|
||||
}
|
||||
return tag["eng"]!;
|
||||
});
|
||||
tagList.forEach((tag, index) => {
|
||||
if (index !== tagList.findLastIndex((val) => tag == val)) {
|
||||
throw new Error(`Duplicated tag "${tag}" found in multiple tag-categories`);
|
||||
}
|
||||
});
|
||||
return [
|
||||
category.data.name,
|
||||
category.id,
|
||||
tagList.filter((tag) => {
|
||||
if (draftOnlyTagsSet.has(tag)) {
|
||||
console.log(`Omitting draft-only tag "${tag}"`);
|
||||
return false;
|
||||
}
|
||||
if (tagsSet.has(tag)) {
|
||||
uncategorizedTagsSet.delete(tag);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
if (uncategorizedTagsSet.size > 0) {
|
||||
const tagList = [...uncategorizedTagsSet];
|
||||
|
|
@ -102,10 +92,10 @@ if (uncategorizedTagsSet.size > 0) {
|
|||
<h2 id="category-series" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Series</h2>
|
||||
<ul class="list-disc pl-8">
|
||||
{
|
||||
Object.entries(seriesList).map(([series, url]) => (
|
||||
seriesCollection.map((series) => (
|
||||
<li>
|
||||
<a class="text-link underline" href={url}>
|
||||
{series}
|
||||
<a class="text-link underline" href={series.data.url}>
|
||||
{series.data.name}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue