Improve i18n support and config validation
This commit is contained in:
parent
4f83ae8802
commit
37db38b613
9 changed files with 152 additions and 152 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "gallery-badmanners-xyz",
|
"name": "gallery-badmanners-xyz",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gallery-badmanners-xyz",
|
"name": "gallery-badmanners-xyz",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.5.9",
|
"@astrojs/check": "^0.5.9",
|
||||||
"@astrojs/rss": "^4.0.5",
|
"@astrojs/rss": "^4.0.5",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gallery-badmanners-xyz",
|
"name": "gallery-badmanners-xyz",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
|
|
|
@ -15,15 +15,11 @@ if (user.data.isAnonymous) {
|
||||||
const username = t(lang, user.data.nameLang as any) || user.data.name;
|
const username = t(lang, user.data.nameLang as any) || user.data.name;
|
||||||
let link: string | null = null;
|
let link: string | null = null;
|
||||||
if (user.data.preferredLink) {
|
if (user.data.preferredLink) {
|
||||||
if (user.data.preferredLink in user.data.links) {
|
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
if (typeof preferredLink === "string") {
|
||||||
if (typeof preferredLink === "string") {
|
link = preferredLink;
|
||||||
link = preferredLink;
|
|
||||||
} else {
|
|
||||||
link = preferredLink[0];
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`No preferredLink "${user.data.preferredLink}" for user ${user.id}`);
|
link = preferredLink[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
|
@ -17,6 +17,8 @@ export const WEBSITE_LIST = [
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const localUrlRegex = /^((\/?\.\.(?=\/))+|(\.(?=\/)))?\/?[a-z0-9_-]+(\/[a-z0-9_-]+)*\/?$/;
|
const localUrlRegex = /^((\/?\.\.(?=\/))+|(\.(?=\/)))?\/?[a-z0-9_-]+(\/[a-z0-9_-]+)*\/?$/;
|
||||||
|
const refineAuthors = (value: { id: any } | any[]) => "id" in value || value.length > 0;
|
||||||
|
const refineCopyrightedCharacters = (value: Record<string, any>) => !("" in value) || Object.keys(value).length == 1;
|
||||||
|
|
||||||
const lang = z.enum(["eng", "tok"]).default("eng");
|
const lang = z.enum(["eng", "tok"]).default("eng");
|
||||||
const website = z.enum(WEBSITE_LIST);
|
const website = z.enum(WEBSITE_LIST);
|
||||||
|
@ -43,7 +45,10 @@ const storiesCollection = defineCollection({
|
||||||
// Optional
|
// Optional
|
||||||
isDraft: z.boolean().default(false),
|
isDraft: z.boolean().default(false),
|
||||||
shortTitle: z.string().optional(),
|
shortTitle: z.string().optional(),
|
||||||
authors: z.union([reference("users"), z.array(reference("users"))]).default("bad-manners"),
|
authors: z
|
||||||
|
.union([reference("users"), z.array(reference("users"))])
|
||||||
|
.default("bad-manners")
|
||||||
|
.refine(refineAuthors, "authors cannot be empty"),
|
||||||
descriptionPlaintext: z.string().optional(),
|
descriptionPlaintext: z.string().optional(),
|
||||||
summary: z.string().optional(),
|
summary: z.string().optional(),
|
||||||
thumbnail: image().optional(),
|
thumbnail: image().optional(),
|
||||||
|
@ -52,7 +57,13 @@ const storiesCollection = defineCollection({
|
||||||
series: reference("series").optional(),
|
series: reference("series").optional(),
|
||||||
commissioner: reference("users").optional(),
|
commissioner: reference("users").optional(),
|
||||||
requester: reference("users").optional(),
|
requester: reference("users").optional(),
|
||||||
copyrightedCharacters: z.record(z.string(), reference("users")).default({}),
|
copyrightedCharacters: z
|
||||||
|
.record(z.string(), reference("users"))
|
||||||
|
.default({})
|
||||||
|
.refine(
|
||||||
|
refineCopyrightedCharacters,
|
||||||
|
"copyrightedCharacters cannot have an empty catch-all key with other keys",
|
||||||
|
),
|
||||||
lang,
|
lang,
|
||||||
prev: reference("stories").nullish(),
|
prev: reference("stories").nullish(),
|
||||||
next: reference("stories").nullish(),
|
next: reference("stories").nullish(),
|
||||||
|
@ -74,13 +85,22 @@ const gamesCollection = defineCollection({
|
||||||
tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
// Optional
|
// Optional
|
||||||
isDraft: z.boolean().default(false),
|
isDraft: z.boolean().default(false),
|
||||||
authors: z.union([reference("users"), z.array(reference("users"))]).default("bad-manners"),
|
authors: z
|
||||||
|
.union([reference("users"), z.array(reference("users"))])
|
||||||
|
.default("bad-manners")
|
||||||
|
.refine(refineAuthors, "authors cannot be empty"),
|
||||||
descriptionPlaintext: z.string().optional(),
|
descriptionPlaintext: z.string().optional(),
|
||||||
thumbnail: image().optional(),
|
thumbnail: image().optional(),
|
||||||
thumbnailWidth: z.number().int().optional(),
|
thumbnailWidth: z.number().int().optional(),
|
||||||
thumbnailHeight: z.number().int().optional(),
|
thumbnailHeight: z.number().int().optional(),
|
||||||
series: reference("series").optional(),
|
series: reference("series").optional(),
|
||||||
copyrightedCharacters: z.record(z.string(), reference("users")).default({}),
|
copyrightedCharacters: z
|
||||||
|
.record(z.string(), reference("users"))
|
||||||
|
.default({})
|
||||||
|
.refine(
|
||||||
|
refineCopyrightedCharacters,
|
||||||
|
"copyrightedCharacters cannot have an empty catch-all key with other keys",
|
||||||
|
),
|
||||||
lang,
|
lang,
|
||||||
relatedStories: z.array(reference("stories")).default([]),
|
relatedStories: z.array(reference("stories")).default([]),
|
||||||
relatedGames: z.array(reference("games")).default([]),
|
relatedGames: z.array(reference("games")).default([]),
|
||||||
|
@ -91,16 +111,24 @@ const gamesCollection = defineCollection({
|
||||||
const usersCollection = defineCollection({
|
const usersCollection = defineCollection({
|
||||||
type: "data",
|
type: "data",
|
||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
z.object({
|
z
|
||||||
// Required
|
.object({
|
||||||
name: z.string(),
|
// Required
|
||||||
links: z.record(website, z.union([z.string().url(), z.tuple([z.string().url(), z.string()])])),
|
name: z.string(),
|
||||||
// Optional
|
links: z.record(website, z.union([z.string().url(), z.tuple([z.string().url(), z.string()])])),
|
||||||
preferredLink: website.nullish(),
|
// Optional
|
||||||
nameLang: z.record(lang, z.string()).default({}),
|
preferredLink: website.nullish(),
|
||||||
avatar: image().optional(),
|
nameLang: z.record(lang, z.string()).default({}),
|
||||||
isAnonymous: z.boolean().default(false),
|
avatar: image().optional(),
|
||||||
}),
|
isAnonymous: z.boolean().default(false),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
({ links, preferredLink }) => !preferredLink || preferredLink in links,
|
||||||
|
({ preferredLink }) => ({
|
||||||
|
message: `"${preferredLink}" not defined in links`,
|
||||||
|
path: ["preferredLink"],
|
||||||
|
}),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const seriesCollection = defineCollection({
|
const seriesCollection = defineCollection({
|
||||||
|
@ -118,7 +146,12 @@ const tagCategoriesCollection = defineCollection({
|
||||||
// Required
|
// Required
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
index: z.number().int(),
|
index: z.number().int(),
|
||||||
tags: z.array(z.union([z.string(), z.record(lang, z.string())])),
|
tags: z.array(
|
||||||
|
z.union([
|
||||||
|
z.string(),
|
||||||
|
z.record(lang, z.string()).refine((tag) => "eng" in tag, 'Object-formatted tag must have an "eng" key'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,29 @@ export type TranslationRecord = { [DEFAULT_LANG]: string | ((...args: any[]) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UI_STRINGS: Record<string, TranslationRecord> = {
|
export const UI_STRINGS: Record<string, TranslationRecord> = {
|
||||||
|
"util/join_names": {
|
||||||
|
eng: (names: string[]) =>
|
||||||
|
names.length <= 1
|
||||||
|
? names.join("")
|
||||||
|
: names.length == 2
|
||||||
|
? `${names[0]} and ${names[1]}`
|
||||||
|
: `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`,
|
||||||
|
tok: (names: string[]) => names.join(" en "),
|
||||||
|
},
|
||||||
|
"export_story/warnings": {
|
||||||
|
eng: (wordCount: number | string, contentWarning: string) => `*Word count: ${wordCount}. ${contentWarning}*`,
|
||||||
|
tok: (_wordCount: number | string, contentWarning: string) => `*${contentWarning}*`,
|
||||||
|
},
|
||||||
|
"export_story/writing": {
|
||||||
|
eng: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`,
|
||||||
|
tok: (authorsList: string[]) => `lipu ni li tan jan ni: ${authorsList.join(" en ")}`,
|
||||||
|
},
|
||||||
|
"export_story/request_for": {
|
||||||
|
eng: (requester: string) => `Request for: ${requester}`,
|
||||||
|
},
|
||||||
|
"export_story/commissioned_by": {
|
||||||
|
eng: (commissioner: string) => `Commissioned by: ${commissioner}`,
|
||||||
|
},
|
||||||
"story/return_to_stories": {
|
"story/return_to_stories": {
|
||||||
eng: "Return to stories",
|
eng: "Return to stories",
|
||||||
tok: "o tawa e lipu ale",
|
tok: "o tawa e lipu ale",
|
||||||
|
@ -59,24 +82,12 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
|
||||||
tok: "lipu lawa",
|
tok: "lipu lawa",
|
||||||
},
|
},
|
||||||
"story/authors": {
|
"story/authors": {
|
||||||
eng: (authorsList: string[]) => {
|
eng: (authorsList: string[]) =>
|
||||||
let authorsString = `by ${authorsList[0]}`;
|
`by ${(UI_STRINGS["util/join_names"]!.eng as (arg: string[]) => string)(authorsList)}`,
|
||||||
if (authorsList.length > 2) {
|
tok: (authorsList: string[]) =>
|
||||||
authorsString += `, ${authorsList.slice(1, authorsList.length - 1).join(", ")}, and ${authorsList[authorsList.length - 1]}`;
|
authorsList.length > 1
|
||||||
} else if (authorsList.length == 2) {
|
? `lipu ni li tan jan ni: ${(UI_STRINGS["util/join_names"]!.tok as (arg: string[]) => string)(authorsList)}`
|
||||||
authorsString += ` and ${authorsList[1]}`;
|
: `lipu ni li tan ${authorsList[0]}`,
|
||||||
}
|
|
||||||
return authorsString;
|
|
||||||
},
|
|
||||||
tok: (authorsList: string[]) => {
|
|
||||||
let authorsString = "lipu ni li tan ";
|
|
||||||
if (authorsList.length > 1) {
|
|
||||||
authorsString += `jan ni: ${authorsList.join(" en ")}`;
|
|
||||||
} else {
|
|
||||||
authorsString += authorsList[0];
|
|
||||||
}
|
|
||||||
return authorsString;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"story/commissioned_by": {
|
"story/commissioned_by": {
|
||||||
eng: (arg: string) => `Commissioned by ${arg}`,
|
eng: (arg: string) => `Commissioned by ${arg}`,
|
||||||
|
@ -85,15 +96,10 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
|
||||||
eng: (arg: string) => `Requested by ${arg}`,
|
eng: (arg: string) => `Requested by ${arg}`,
|
||||||
},
|
},
|
||||||
"characters/characters_are_copyrighted_by": {
|
"characters/characters_are_copyrighted_by": {
|
||||||
eng: (owner: string, charactersList: string[]) => {
|
eng: (owner: string, charactersList: string[]) =>
|
||||||
if (charactersList.length == 1) {
|
charactersList.length == 1
|
||||||
return `${charactersList[0]} is © ${owner}`;
|
? `${charactersList[0]} is © ${owner}`
|
||||||
}
|
: `${(UI_STRINGS["util/join_names"]!["eng"] as (arg: string[]) => string)(charactersList)} are © ${owner}`,
|
||||||
if (charactersList.length == 2) {
|
|
||||||
return `${charactersList[0]} and ${charactersList[1]} are © ${owner}`;
|
|
||||||
}
|
|
||||||
return `${charactersList.slice(0, -1).join(", ")}, and ${charactersList[charactersList.length - 1]} are © ${owner}`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"characters/all_characters_are_copyrighted_by": {
|
"characters/all_characters_are_copyrighted_by": {
|
||||||
eng: (owner: string) => `All characters are © ${owner}`,
|
eng: (owner: string) => `All characters are © ${owner}`,
|
||||||
|
|
|
@ -16,9 +16,6 @@ type Props = CollectionEntry<"games">["data"];
|
||||||
const { props } = Astro;
|
const { props } = Astro;
|
||||||
const series = props.series && (await getEntry(props.series));
|
const series = props.series && (await getEntry(props.series));
|
||||||
const authors = await getEntries([props.authors].flat());
|
const authors = await getEntries([props.authors].flat());
|
||||||
if ("" in props.copyrightedCharacters && Object.keys(props.copyrightedCharacters).length > 1) {
|
|
||||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
|
||||||
}
|
|
||||||
const copyrightedCharacters = await Promise.all(
|
const copyrightedCharacters = await Promise.all(
|
||||||
Object.values(
|
Object.values(
|
||||||
Object.keys(props.copyrightedCharacters).reduce(
|
Object.keys(props.copyrightedCharacters).reduce(
|
||||||
|
|
|
@ -26,9 +26,6 @@ const series = props.series && (await getEntry(props.series));
|
||||||
const authors = await getEntries([props.authors].flat());
|
const authors = await getEntries([props.authors].flat());
|
||||||
const commissioner = props.commissioner && (await getEntry(props.commissioner));
|
const commissioner = props.commissioner && (await getEntry(props.commissioner));
|
||||||
const requester = props.requester && (await getEntry(props.requester));
|
const requester = props.requester && (await getEntry(props.requester));
|
||||||
if ("" in props.copyrightedCharacters && Object.keys(props.copyrightedCharacters).length > 1) {
|
|
||||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
|
||||||
}
|
|
||||||
const copyrightedCharacters = await Promise.all(
|
const copyrightedCharacters = await Promise.all(
|
||||||
Object.values(
|
Object.values(
|
||||||
Object.keys(props.copyrightedCharacters).reduce(
|
Object.keys(props.copyrightedCharacters).reduce(
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { decode as tinyDecode } from "tiny-decode";
|
||||||
import { type Lang, type Website } from "../../../content/config";
|
import { type Lang, type Website } from "../../../content/config";
|
||||||
import { t } from "../../../i18n";
|
import { t } from "../../../i18n";
|
||||||
|
|
||||||
type DescriptionFormat = "bbcode" | "markdown";
|
type ExportFormat = "bbcode" | "markdown";
|
||||||
|
|
||||||
const WEBSITE_LIST = [
|
const WEBSITE_LIST = [
|
||||||
["eka", "bbcode"],
|
["eka", "bbcode"],
|
||||||
|
@ -13,9 +13,9 @@ const WEBSITE_LIST = [
|
||||||
["inkbunny", "bbcode"],
|
["inkbunny", "bbcode"],
|
||||||
["sofurry", "bbcode"],
|
["sofurry", "bbcode"],
|
||||||
["weasyl", "markdown"],
|
["weasyl", "markdown"],
|
||||||
] as const satisfies [Website, DescriptionFormat][];
|
] as const satisfies [Website, ExportFormat][];
|
||||||
|
|
||||||
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<[infer K, DescriptionFormat]> ? K : never;
|
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<[infer K, ExportFormat]> ? K : never;
|
||||||
|
|
||||||
const bbcodeRenderer: RendererApi = {
|
const bbcodeRenderer: RendererApi = {
|
||||||
strong: (text) => `[b]${text}[/b]`,
|
strong: (text) => `[b]${text}[/b]`,
|
||||||
|
@ -108,7 +108,7 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled website "${website}" in getUsernameForWebsite`);
|
throw new Error(`Unhandled Website "${website}"`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return link[1].replace(/^@/, "");
|
return link[1].replace(/^@/, "");
|
||||||
|
@ -117,24 +117,14 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
|
||||||
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
|
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPreferredWebsite(
|
function isPreferredWebsite(user: CollectionEntry<"users">, website: Website): boolean {
|
||||||
user: CollectionEntry<"users">,
|
const { preferredLink } = user.data;
|
||||||
website: Website,
|
return !preferredLink || preferredLink == 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 {
|
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite, anonymousFallback: string): string {
|
||||||
if (user.data.isAnonymous) {
|
if (user.data.isAnonymous) {
|
||||||
return "anonymous";
|
return anonymousFallback;
|
||||||
}
|
}
|
||||||
switch (website) {
|
switch (website) {
|
||||||
case "eka":
|
case "eka":
|
||||||
|
@ -148,51 +138,46 @@ function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite):
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "weasyl":
|
case "weasyl":
|
||||||
const weasylPreferredWebsites = ["furaffinity", "inkbunny", "sofurry"] as const;
|
|
||||||
if ("weasyl" in user.data.links) {
|
if ("weasyl" in user.data.links) {
|
||||||
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
|
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
|
||||||
} else if (isPreferredWebsite(user, "furaffinity", weasylPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||||
return `<fa:${getUsernameForWebsite(user, "furaffinity")}>`;
|
return `<fa:${getUsernameForWebsite(user, "furaffinity")}>`;
|
||||||
} else if (isPreferredWebsite(user, "inkbunny", weasylPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||||
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
|
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
|
||||||
} else if (isPreferredWebsite(user, "sofurry", weasylPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||||
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
|
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "inkbunny":
|
case "inkbunny":
|
||||||
const inkbunnyPreferredWebsites = ["furaffinity", "sofurry", "weasyl"] as const;
|
|
||||||
if ("inkbunny" in user.data.links) {
|
if ("inkbunny" in user.data.links) {
|
||||||
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
|
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
|
||||||
} else if (isPreferredWebsite(user, "furaffinity", inkbunnyPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||||
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
|
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
|
||||||
} else if (isPreferredWebsite(user, "sofurry", inkbunnyPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||||
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
|
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
|
||||||
} else if (isPreferredWebsite(user, "weasyl", inkbunnyPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "weasyl")) {
|
||||||
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
|
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "sofurry":
|
case "sofurry":
|
||||||
const sofurryPreferredWebsites = ["furaffinity", "inkbunny"] as const;
|
|
||||||
if ("sofurry" in user.data.links) {
|
if ("sofurry" in user.data.links) {
|
||||||
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
|
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
|
||||||
} else if (isPreferredWebsite(user, "furaffinity", sofurryPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||||
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
|
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
|
||||||
} else if (isPreferredWebsite(user, "inkbunny", sofurryPreferredWebsites)) {
|
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||||
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
|
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled website "${website}" in getLinkForUser`);
|
throw new Error(`Unhandled ExportWebsite "${website}"`);
|
||||||
}
|
}
|
||||||
if (user.data.preferredLink) {
|
if (user.data.preferredLink) {
|
||||||
if (user.data.preferredLink in user.data.links) {
|
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
return `[${user.data.name}](${typeof preferredLink === "string" ? preferredLink : preferredLink[0]})`;
|
||||||
return `[${user.data.name}](${typeof preferredLink === "string" ? preferredLink : preferredLink[0]})`;
|
|
||||||
} else {
|
|
||||||
throw new Error(`No preferredLink "${user.data.preferredLink}" for user ${user.id}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new Error(`No "${website}" link for user "${user.id}" (consider setting preferredLink)`);
|
throw new Error(
|
||||||
|
`No matching "${website}" link for user "${user.id}" (consider setting their "preferredLink" property)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
|
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
|
||||||
|
@ -222,55 +207,47 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
|
|
||||||
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
|
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
|
||||||
const { lang } = story.data;
|
const { lang } = story.data;
|
||||||
if (
|
const copyrightedCharacters = await Promise.all(
|
||||||
story.data.copyrightedCharacters &&
|
Object.values(
|
||||||
"" in story.data.copyrightedCharacters &&
|
Object.keys(story.data.copyrightedCharacters).reduce(
|
||||||
Object.keys(story.data.copyrightedCharacters).length > 1
|
(acc, character) => {
|
||||||
) {
|
const user = story.data.copyrightedCharacters[character];
|
||||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
if (!(user.id in acc)) {
|
||||||
}
|
acc[user.id] = [getEntry(user), []];
|
||||||
const charactersPerUser =
|
}
|
||||||
story.data.copyrightedCharacters &&
|
acc[user.id][1].push(character);
|
||||||
Object.keys(story.data.copyrightedCharacters).reduce(
|
return acc;
|
||||||
(acc, character) => {
|
},
|
||||||
const key = story.data.copyrightedCharacters[character].id;
|
{} as Record<string, [Promise<CollectionEntry<"users">>, string[]]>,
|
||||||
if (!(key in acc)) {
|
),
|
||||||
acc[key] = [];
|
).map(async ([userPromise, characters]) => [await userPromise, characters] as [CollectionEntry<"users">, string[]]),
|
||||||
}
|
);
|
||||||
acc[key].push(character);
|
const authorsList = await getEntries([story.data.authors].flat());
|
||||||
return acc;
|
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
||||||
},
|
const requester = story.data.requester && (await getEntry(story.data.requester));
|
||||||
{} as Record<
|
const anonymousUser = await getEntry("users", "anonymous");
|
||||||
CollectionEntry<"users">["id"],
|
const anonymousFallback = getNameForUser(anonymousUser, anonymousUser, lang);
|
||||||
(typeof story.data.copyrightedCharacters extends Record<infer K, any> ? K : never)[]
|
|
||||||
>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const description: Record<ExportWebsite, string> = Object.fromEntries(
|
const description: Record<ExportWebsite, string> = Object.fromEntries(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
WEBSITE_LIST.map(async ([website, exportFormat]) => {
|
WEBSITE_LIST.map(async ([website, exportFormat]) => {
|
||||||
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
|
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website, anonymousFallback);
|
||||||
const storyDescription = (
|
const storyDescription = (
|
||||||
[
|
[
|
||||||
story.data.description,
|
story.data.description,
|
||||||
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
|
t(lang, "export_story/warnings", story.data.wordCount, story.data.contentWarning.trim()),
|
||||||
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" "),
|
t(
|
||||||
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
|
lang,
|
||||||
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
|
"export_story/writing",
|
||||||
...(await Promise.all(
|
authorsList.map((author) => u(author)),
|
||||||
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
|
),
|
||||||
const user = u(await getEntry("users", id));
|
requester && t(lang, "export_story/request_for", u(requester)),
|
||||||
const characterList = charactersPerUser[id];
|
commissioner && t(lang, "export_story/commissioned_by", u(commissioner)),
|
||||||
if (characterList[0] == "") {
|
...copyrightedCharacters.map(([user, characterList]) =>
|
||||||
return `All characters are © ${user}`;
|
characterList[0] == ""
|
||||||
} else if (characterList.length > 2) {
|
? t(lang, "characters/all_characters_are_copyrighted_by", u(user))
|
||||||
return `${characterList.slice(0, characterList.length - 1).join(", ")}, and ${characterList[characterList.length - 1]} are © ${user}`;
|
: t(lang, "characters/characters_are_copyrighted_by", u(user), characterList),
|
||||||
} else if (characterList.length > 1) {
|
),
|
||||||
return `${characterList[0]} and ${characterList[1]} are © ${user}`;
|
|
||||||
}
|
|
||||||
return `${characterList[0]} is © ${user}`;
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
].filter((data) => data) as string[]
|
].filter((data) => data) as string[]
|
||||||
)
|
)
|
||||||
.join("\n\n")
|
.join("\n\n")
|
||||||
|
@ -290,25 +267,22 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
||||||
if (exportFormat === "markdown") {
|
if (exportFormat === "markdown") {
|
||||||
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
|
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
|
||||||
}
|
}
|
||||||
throw new Error(`Unknown exportFormat "${exportFormat}"`);
|
throw new Error(`Unhandled ExportFormat "${exportFormat}"`);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const anonymousUser = await getEntry("users", "anonymous");
|
|
||||||
const authorsNames = (await getEntries([story.data.authors].flat())).map((author) =>
|
|
||||||
getNameForUser(author, anonymousUser, lang),
|
|
||||||
);
|
|
||||||
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
|
||||||
const requester = story.data.requester && (await getEntry(story.data.requester));
|
|
||||||
|
|
||||||
const storyHeader =
|
const storyHeader =
|
||||||
`${story.data.title}\n` +
|
`${story.data.title}\n` +
|
||||||
`${t(lang, "story/authors", authorsNames)}\n` +
|
`${t(
|
||||||
|
lang,
|
||||||
|
"story/authors",
|
||||||
|
authorsList.map((author) => getNameForUser(author, anonymousUser, lang)),
|
||||||
|
)}\n` +
|
||||||
(commissioner ? `${t(lang, "story/commissioned_by", getNameForUser(commissioner, anonymousUser, lang))}\n` : "") +
|
(commissioner ? `${t(lang, "story/commissioned_by", getNameForUser(commissioner, anonymousUser, lang))}\n` : "") +
|
||||||
(requester ? `${t(lang, "story/requested_by", getNameForUser(requester, anonymousUser, lang))}\n` : "");
|
(requester ? `${t(lang, "story/requested_by", getNameForUser(requester, anonymousUser, lang))}\n` : "");
|
||||||
|
|
||||||
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll("\\*", "*").replaceAll("\\=", "=")}`
|
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll(/\\([=*])/g, "$1")}`
|
||||||
.replaceAll(/\n\n\n+/g, "\n\n")
|
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,6 @@ const categorizedTags: Array<[string, string, string[]]> = tagCategories
|
||||||
if (typeof tag === "string") {
|
if (typeof tag === "string") {
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
if (!("eng" in tag)) {
|
|
||||||
throw new Error(`Object-formatted tag must have an "eng" key: ${tag}`);
|
|
||||||
}
|
|
||||||
return tag["eng"]!;
|
return tag["eng"]!;
|
||||||
});
|
});
|
||||||
tagList.forEach((tag, index) => {
|
tagList.forEach((tag, index) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue