Add qualifyLocalURLsInMarkdown()
This will handle special links in the description and similar fields
This commit is contained in:
parent
c38275e2f2
commit
cd67f6a5c5
20 changed files with 982 additions and 296 deletions
|
|
@ -1,14 +1,16 @@
|
|||
import type { APIRoute, GetStaticPaths } from "astro";
|
||||
import { getCollection, type CollectionEntry, getEntries } from "astro:content";
|
||||
import type { Website } from "../../../content/config";
|
||||
import { t, type Lang } from "../../../i18n";
|
||||
import type { PostWebsite } from "../../../content/config";
|
||||
import { t } from "../../../i18n";
|
||||
import { formatCopyrightedCharacters } from "../../../utils/format_copyrighted_characters";
|
||||
import { markdownToBbcode } from "../../../utils/markdown_to_bbcode";
|
||||
import { getUsernameForLang } from "../../../utils/get_username_for_lang";
|
||||
import { isAnonymousUser } from "../../../utils/is_anonymous_user";
|
||||
import { qualifyLocalURLsInMarkdown } from "../../../utils/qualify_local_urls_in_markdown";
|
||||
import { getWebsiteLinkForUser } from "../../../utils/get_website_link_for_user";
|
||||
|
||||
interface ExportWebsiteInfo {
|
||||
website: Website;
|
||||
website: PostWebsite;
|
||||
exportFormat: "bbcode" | "markdown";
|
||||
}
|
||||
|
||||
|
|
@ -22,76 +24,6 @@ const WEBSITE_LIST = [
|
|||
|
||||
type ExportWebsiteName = typeof WEBSITE_LIST extends ReadonlyArray<{ website: infer K }> ? K : never;
|
||||
|
||||
function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website): string {
|
||||
const link = user.data.links[website];
|
||||
if (link && "username" in link && link.username) {
|
||||
return link.username;
|
||||
}
|
||||
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
|
||||
}
|
||||
|
||||
function isPreferredWebsite(user: CollectionEntry<"users">, website: Website): boolean {
|
||||
const { preferredLink } = user.data;
|
||||
return !preferredLink || preferredLink == website;
|
||||
}
|
||||
|
||||
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsiteName, lang: Lang): string {
|
||||
const { links, preferredLink } = user.data;
|
||||
switch (website) {
|
||||
case "eka":
|
||||
if ("eka" in links) {
|
||||
return `:icon${getUsernameForWebsite(user, "eka")}:`;
|
||||
}
|
||||
break;
|
||||
case "furaffinity":
|
||||
if ("furaffinity" in links) {
|
||||
return `:icon${getUsernameForWebsite(user, "furaffinity")}:`;
|
||||
}
|
||||
break;
|
||||
case "weasyl":
|
||||
if ("weasyl" in links) {
|
||||
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `<fa:${getUsernameForWebsite(user, "furaffinity").replaceAll("_", "")}>`;
|
||||
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
|
||||
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
|
||||
}
|
||||
break;
|
||||
case "inkbunny":
|
||||
if ("inkbunny" in links) {
|
||||
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
|
||||
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
|
||||
} else if (isPreferredWebsite(user, "weasyl")) {
|
||||
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
|
||||
}
|
||||
break;
|
||||
case "sofurry":
|
||||
if ("sofurry" in links) {
|
||||
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
|
||||
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
const unknown: never = website;
|
||||
throw new Error(`Unhandled export website "${unknown}"`);
|
||||
}
|
||||
if (preferredLink) {
|
||||
const preferred = links[preferredLink]!;
|
||||
return `[${getUsernameForLang(user, lang)}](${preferred.link})`;
|
||||
}
|
||||
throw new Error(
|
||||
`No matching "${website}" link for user "${user.id}" (consider setting their "preferredLink" property)`,
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
story: CollectionEntry<"stories">;
|
||||
};
|
||||
|
|
@ -111,18 +43,21 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
|||
};
|
||||
|
||||
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
|
||||
const { lang } = story.data;
|
||||
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
|
||||
const authorsList = await getEntries(story.data.authors);
|
||||
const commissionersList = story.data.commissioner && (await getEntries(story.data.commissioner));
|
||||
const requestersList = story.data.requester && (await getEntries(story.data.requester));
|
||||
try {
|
||||
const { lang } = story.data;
|
||||
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
|
||||
const authorsList = await getEntries(story.data.authors);
|
||||
const commissionersList = story.data.commissioner && (await getEntries(story.data.commissioner));
|
||||
const requestersList = story.data.requester && (await getEntries(story.data.requester));
|
||||
|
||||
const description = Object.fromEntries(
|
||||
WEBSITE_LIST.map<[ExportWebsiteName, string]>(({ website, exportFormat }) => {
|
||||
const u = (user: CollectionEntry<"users">) =>
|
||||
isAnonymousUser(user) ? getUsernameForLang(user, lang) : getLinkForUser(user, website, lang);
|
||||
const storyDescription = (
|
||||
[
|
||||
const description = await Promise.all(
|
||||
WEBSITE_LIST.map(async ({ website, exportFormat }) => {
|
||||
const exportWebsite: ExportWebsiteName = website;
|
||||
const u = (user: CollectionEntry<"users">) =>
|
||||
isAnonymousUser(user)
|
||||
? getUsernameForLang(user, lang)
|
||||
: getWebsiteLinkForUser(user, exportWebsite, (user) => getUsernameForLang(user, lang));
|
||||
const storyDescription = await [
|
||||
story.data.description,
|
||||
`*${t(lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}*`,
|
||||
t(
|
||||
|
|
@ -142,62 +77,76 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
|||
"export_story/commissioned_by",
|
||||
commissionersList.map((commissioner) => u(commissioner)),
|
||||
),
|
||||
...copyrightedCharacters.map(([user, characterList]) =>
|
||||
characterList[0] == ""
|
||||
? t(lang, "characters/all_characters_are_copyrighted_by", u(user))
|
||||
: t(lang, "characters/characters_are_copyrighted_by", u(user), characterList),
|
||||
...copyrightedCharacters.map(({ user, characters }) =>
|
||||
t(lang, "characters/characters_are_copyrighted_by", u(user), characters[0] == "" ? [] : characters),
|
||||
),
|
||||
].filter((data) => data) as string[]
|
||||
)
|
||||
.join("\n\n")
|
||||
.replaceAll(
|
||||
/\[([^\]]+)\]\((\/[^\)]+)\)/g,
|
||||
(_, group1, group2) => `[${group1}](${new URL(group2, site).toString()})`,
|
||||
);
|
||||
switch (exportFormat) {
|
||||
case "bbcode":
|
||||
return [website, markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n")];
|
||||
case "markdown":
|
||||
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
|
||||
default:
|
||||
const unknown: never = exportFormat;
|
||||
throw new Error(`Unknown export format "${unknown}"`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
].reduce(async (promise, data) => {
|
||||
if (!data) {
|
||||
return promise;
|
||||
}
|
||||
const acc = await promise;
|
||||
const newData = await qualifyLocalURLsInMarkdown(data, lang, site, exportWebsite);
|
||||
return acc ? `${acc}\n\n${newData}` : newData;
|
||||
}, Promise.resolve(""));
|
||||
switch (exportFormat) {
|
||||
case "bbcode":
|
||||
return { exportWebsite, description: markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n") };
|
||||
case "markdown":
|
||||
return { exportWebsite, description: storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim() };
|
||||
default:
|
||||
const unknown: never = exportFormat;
|
||||
throw new Error(`Unknown export format "${unknown}"`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const storyHeader =
|
||||
`${story.data.title}\n` +
|
||||
`${t(
|
||||
lang,
|
||||
"story/authors",
|
||||
authorsList.map((author) => getUsernameForLang(author, lang)),
|
||||
)}\n` +
|
||||
(requestersList
|
||||
? `${t(
|
||||
lang,
|
||||
"story/requested_by",
|
||||
requestersList.map((requester) => getUsernameForLang(requester, lang)),
|
||||
)}\n`
|
||||
: "") +
|
||||
(commissionersList
|
||||
? `${t(
|
||||
lang,
|
||||
"story/commissioned_by",
|
||||
commissionersList.map((commissioner) => getUsernameForLang(commissioner, lang)),
|
||||
)}\n`
|
||||
: "");
|
||||
const storyHeader =
|
||||
`${story.data.title}\n` +
|
||||
`${t(
|
||||
lang,
|
||||
"story/authors",
|
||||
authorsList.map((author) => getUsernameForLang(author, lang)),
|
||||
)}\n` +
|
||||
(requestersList
|
||||
? `${t(
|
||||
lang,
|
||||
"story/requested_by",
|
||||
requestersList.map((requester) => getUsernameForLang(requester, lang)),
|
||||
)}\n`
|
||||
: "") +
|
||||
(commissionersList
|
||||
? `${t(
|
||||
lang,
|
||||
"story/commissioned_by",
|
||||
commissionersList.map((commissioner) => getUsernameForLang(commissioner, lang)),
|
||||
)}\n`
|
||||
: "");
|
||||
|
||||
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll(/\\([=*])/g, "$1")}`
|
||||
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||
.trim();
|
||||
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll(/\\([=*])/g, "$1")}`
|
||||
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||
.trim();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
story: storyText,
|
||||
description,
|
||||
thumbnail: story.data.thumbnail ? story.data.thumbnail.src : null,
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json; charset=utf-8" } },
|
||||
);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
story: storyText,
|
||||
description: description.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.exportWebsite] = item.description;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<PostWebsite, string>,
|
||||
),
|
||||
thumbnail: story.data.thumbnail ? story.data.thumbnail.src : null,
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json; charset=utf-8" } },
|
||||
);
|
||||
} catch (e) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: (e as Error).message ?? null,
|
||||
stack: (e as Error).stack ?? null,
|
||||
}),
|
||||
{ status: 500, headers: { "Content-Type": "application/json; charset=utf-8" } },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue