Replace /api/share with / + errors

This commit is contained in:
Nikita Karamov 2023-09-02 19:02:17 +02:00
parent 9828b123d9
commit 725d7316c4
No known key found for this signature in database
GPG key ID: 41D6F71EE78E77CD
6 changed files with 97 additions and 42 deletions

View file

@ -32,7 +32,8 @@ now takes seconds! This comes with changes, though:
Some changes came with the name change: Some changes came with the name change:
- **changed API endpoint path**: ~~`/api/toot`~~`/api/share` - **changed API endpoint path**: ~~`/api/toot`~~`/`
- just send a POST request instead of a GET request with the same body
### Added ### Added

View file

@ -6,7 +6,7 @@
* SPDX-FileCopyrightText: © 2023 Nikita Karamov <me@kytta.dev> * SPDX-FileCopyrightText: © 2023 Nikita Karamov <me@kytta.dev>
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
const { prefilledInstance } = Astro.props; const { instance, errors } = Astro.props;
--- ---
<datalist id="popular-instances"></datalist> <datalist id="popular-instances"></datalist>
@ -20,10 +20,23 @@ const { prefilledInstance } = Astro.props;
id="instance" id="instance"
list="popular-instances" list="popular-instances"
required required
aria-invalid={Boolean(errors)}
aria-errormessage={errors ? "instance-error" : undefined}
aria-describedby="https-label" aria-describedby="https-label"
value={prefilledInstance} value={instance}
/> />
</div> </div>
{
errors && (
<p
class="error"
id="instance-error"
aria-live="assertive"
>
{errors}
</p>
)
}
</label> </label>
<div <div

View file

@ -21,11 +21,11 @@ export const get: APIRoute = async ({ params }) => {
const softwareName = await getSoftwareName(domain); const softwareName = await getSoftwareName(domain);
if (softwareName === undefined) { if (softwareName === undefined) {
return error("Could not detect software; NodeInfo not present."); return error("Could not detect Fediverse project.");
} }
if (!(softwareName in supportedProjects)) { if (!(softwareName in supportedProjects)) {
return error(`"${softwareName}" is not supported yet.`); return error(`Fediverse project "${softwareName}" is not supported yet.`);
} }
const publishConfig = supportedProjects[softwareName] as ProjectPublishConfig; const publishConfig = supportedProjects[softwareName] as ProjectPublishConfig;

View file

@ -1,31 +0,0 @@
/*!
* This file is part of ShareFedi
* https://github.com/kytta/share2fedi
*
* SPDX-FileCopyrightText: © 2023 Nikita Karamov <me@kytta.dev>
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { getUrlDomain } from "@lib/url";
import type { APIRoute } from "astro";
import type { Detection } from "./detect/[domain]";
import { error } from "@lib/response";
export const post: APIRoute = async ({ redirect, request, url }) => {
const formData = await request.formData();
const text = (formData.get("text") as string) || "";
const instanceHost =
getUrlDomain(formData.get("instance") as string) || "mastodon.social";
const response = await fetch(new URL(`/api/detect/${instanceHost}`, url));
const json = await response.json();
if (json.error) {
return error(json.error);
}
const { domain, endpoint, params } = json as Detection;
const publishUrl = new URL(endpoint, `https://${domain}/`);
publishUrl.search = new URLSearchParams([[params.text, text]]).toString();
return redirect(publishUrl.toString(), 303);
};

View file

@ -9,15 +9,56 @@
import Layout from "@layouts/layout.astro"; import Layout from "@layouts/layout.astro";
import InstanceSelect from "@components/instance-select.astro"; import InstanceSelect from "@components/instance-select.astro";
import { getUrlDomain } from "@lib/url";
import type { Detection } from "./api/detect/[domain]";
const searchParameters = new URL(Astro.request.url).searchParams; const searchParameters = new URL(Astro.request.url).searchParams;
const prefilledText = searchParameters.get("text"); let text: string | null = searchParameters.get("text");
const prefilledInstance = searchParameters.get("instance"); let instance: string | null = searchParameters.get("instance");
const errors = { text: "", instance: "" };
if (Astro.request.method === "POST") {
const formData = await Astro.request.formData();
text = formData.get("text") as string | null;
instance = formData.get("instance") as string | null;
if (typeof text !== "string" || text.length === 0) {
errors.text += "Please enter post text. ";
}
let detection: Detection | undefined;
if (typeof instance !== "string" || instance.length === 0) {
errors.instance += "Please enter instance domain. ";
} else {
instance = getUrlDomain(instance);
const detectResponse = await fetch(
new URL(`/api/detect/${instance}`, Astro.url),
);
const detectJson = await detectResponse.json();
if (detectJson.error) {
errors.instance += detectJson.error + " ";
} else {
detection = detectJson;
}
}
const hasErrors = Object.values(errors).some(Boolean);
if (!hasErrors && detection !== undefined) {
const { domain, endpoint, params } = detection;
const publishUrl = new URL(endpoint, `https://${domain}/`);
publishUrl.search = new URLSearchParams([
[params.text, text as string],
]).toString();
// eslint-disable-next-line unicorn/prefer-module
return Astro.redirect(publishUrl.toString(), 303);
}
}
--- ---
<Layout title="Share₂Fedi — an instance-agnostic share page for the Fediverse"> <Layout title="Share₂Fedi — an instance-agnostic share page for the Fediverse">
<form <form
id="form" id="form"
action="/api/share"
method="POST" method="POST"
> >
<label data-translate="postText"> <label data-translate="postText">
@ -28,13 +69,29 @@ const prefilledInstance = searchParameters.get("instance");
rows="7" rows="7"
placeholder="Whats on your mind?" placeholder="Whats on your mind?"
required required
aria-invalid={Boolean(errors.text)}
aria-errormessage={errors.text ? "text-error" : undefined}
data-translate="postTextPlaceholder" data-translate="postTextPlaceholder"
data-translate-attribute="placeholder" data-translate-attribute="placeholder"
>{prefilledText}</textarea >{text}</textarea
> >
{
errors.text && (
<p
class="error"
id="text-error"
aria-live="assertive"
>
{errors.text}
</p>
)
}
</label> </label>
<InstanceSelect {prefilledInstance} /> <InstanceSelect
{instance}
errors={errors.instance}
/>
<input <input
type="submit" type="submit"

View file

@ -18,7 +18,9 @@
--s2f-accent-color: #40665c; --s2f-accent-color: #40665c;
--s2f-accent-color-light: #5d8379; --s2f-accent-color-light: #5d8379;
--s2f-accent-color-contrast: #005e4e; --s2f-accent-color-contrast: #005e4e;
--s2f-error-color: #a12f2d;
--s2f-border-color: #ccc; --s2f-border-color: #ccc;
--s2f-border-width: 1px;
--s2f-input-group-bg-color: #eee; --s2f-input-group-bg-color: #eee;
--s2f-input-bg-color: #fff; --s2f-input-bg-color: #fff;
--s2f-input-text-color: #000; --s2f-input-text-color: #000;
@ -116,7 +118,7 @@ textarea {
width: 100%; width: 100%;
color: var(--s2f-input-text-color); color: var(--s2f-input-text-color);
background-color: var(--s2f-input-bg-color); background-color: var(--s2f-input-bg-color);
border: 1px solid var(--s2f-border-color); border: var(--s2f-border-width) solid var(--s2f-border-color);
} }
input[type="checkbox"], input[type="checkbox"],
@ -163,3 +165,16 @@ input[type="submit"] {
width: math.div(100%, 3); width: math.div(100%, 3);
} }
} }
[aria-invalid="true"],
[aria-errormessage] {
--s2f-input-text-color: var(--s2f-error-color);
--s2f-border-color: var(--s2f-error-color);
accent-color: var(--s2f-error-color);
}
p.error {
color: var(--s2f-error-color);
margin: 0 0 1rem;
}