fix(ci): resolve all lint errors for GitHub Actions
- Remove unused SupportedLocale import in results.tsx - Remove unused webroot variable in public/i18n.js - Configure knip.json to ignore i18n public API exports - Add language-selector and language-option to ESLint ignore list - Auto-fix Prettier formatting across all files - Fix better-tailwindcss line wrapping issues
This commit is contained in:
parent
8089fa0853
commit
569c572e62
16 changed files with 621 additions and 508 deletions
|
|
@ -9,233 +9,245 @@ import { userService } from "./user";
|
|||
import { EyeIcon } from "../icons/eye";
|
||||
import { DeleteIcon } from "../icons/delete";
|
||||
|
||||
export const history = new Elysia().use(userService).use(localeService).get(
|
||||
"/history",
|
||||
async ({ redirect, user, locale, t }) => {
|
||||
if (HIDE_HISTORY) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
export const history = new Elysia()
|
||||
.use(userService)
|
||||
.use(localeService)
|
||||
.get(
|
||||
"/history",
|
||||
async ({ redirect, user, locale, t }) => {
|
||||
if (HIDE_HISTORY) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
let userJobs = db.query("SELECT * FROM jobs WHERE user_id = ?").as(Jobs).all(user.id).reverse();
|
||||
let userJobs = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ?")
|
||||
.as(Jobs)
|
||||
.all(user.id)
|
||||
.reverse();
|
||||
|
||||
for (const job of userJobs) {
|
||||
const files = db.query("SELECT * FROM file_names WHERE job_id = ?").as(Filename).all(job.id);
|
||||
for (const job of userJobs) {
|
||||
const files = db
|
||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||
.as(Filename)
|
||||
.all(job.id);
|
||||
|
||||
job.finished_files = files.length;
|
||||
job.files_detailed = files;
|
||||
}
|
||||
job.finished_files = files.length;
|
||||
job.files_detailed = files;
|
||||
}
|
||||
|
||||
// Filter out jobs with no files
|
||||
userJobs = userJobs.filter((job) => job.num_files > 0);
|
||||
// Filter out jobs with no files
|
||||
userJobs = userJobs.filter((job) => job.num_files > 0);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Results" locale={locale}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
locale={locale}
|
||||
t={t}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">{t("history", "title")}</h1>
|
||||
<div id="delete-selected-container">
|
||||
<button
|
||||
id="delete-selected-btn"
|
||||
class={`
|
||||
flex btn-secondary flex-row gap-2 text-contrast
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
disabled
|
||||
>
|
||||
<DeleteIcon />{" "}
|
||||
<span>
|
||||
{t("history", "deleteSelected")} (<span id="selected-count">0</span>)
|
||||
</span>
|
||||
</button>
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Results" locale={locale}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
locale={locale}
|
||||
t={t}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">{t("history", "title")}</h1>
|
||||
<div id="delete-selected-container">
|
||||
<button
|
||||
id="delete-selected-btn"
|
||||
class={`
|
||||
flex btn-secondary flex-row gap-2 text-contrast
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
disabled
|
||||
>
|
||||
<DeleteIcon />{" "}
|
||||
<span>
|
||||
{t("history", "deleteSelected")} (<span id="selected-count">0</span>)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select-all"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
title={t("history", "selectAll")}
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<span class="sr-only">{t("history", "expandDetails")}</span>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "time")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "files")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
max-sm:hidden
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "filesDone")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "status")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "actions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
data-checkbox-type="job"
|
||||
data-job-id={job.id}
|
||||
/>
|
||||
</td>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="inline-block h-4 w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select-all"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
title={t("history", "selectAll")}
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<span class="sr-only">{t("history", "expandDetails")}</span>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "time")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "files")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
max-sm:hidden
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "filesDone")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "status")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
{t("history", "actions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
data-checkbox-type="job"
|
||||
data-job-id={job.id}
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td safe>
|
||||
{new Date(job.date_created).toLocaleTimeString(LANGUAGE, {
|
||||
timeZone: TIMEZONE,
|
||||
})}
|
||||
</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td class="flex flex-row gap-4">
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/results/${job.id}`}
|
||||
>
|
||||
<EyeIcon />
|
||||
</a>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/delete/${job.id}`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="7">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">{t("history", "detailedFileInfo")}</div>
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
<div class="flex items-center">
|
||||
<span class="w-5/12 truncate" title={file.file_name} safe>
|
||||
{file.file_name}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
||||
{file.output_file_name}
|
||||
</span>
|
||||
</td>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="inline-block h-4 w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td safe>
|
||||
{new Date(job.date_created).toLocaleTimeString(LANGUAGE, {
|
||||
timeZone: TIMEZONE,
|
||||
})}
|
||||
</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td class="flex flex-row gap-4">
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/results/${job.id}`}
|
||||
>
|
||||
<EyeIcon />
|
||||
</a>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/delete/${job.id}`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="7">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">
|
||||
{t("history", "detailedFileInfo")}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script>
|
||||
{`
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
<div class="flex items-center">
|
||||
<span class="w-5/12 truncate" title={file.file_name} safe>
|
||||
{file.file_name}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
||||
{file.output_file_name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script>
|
||||
{`
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Expand/collapse job details
|
||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||
|
|
@ -330,12 +342,12 @@ export const history = new Elysia().use(userService).use(localeService).get(
|
|||
});
|
||||
});
|
||||
`}
|
||||
</script>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
|||
import { DownloadIcon } from "../icons/download";
|
||||
import { DeleteIcon } from "../icons/delete";
|
||||
import { EyeIcon } from "../icons/eye";
|
||||
import { type SupportedLocale, type Translator, createTranslator, defaultLocale } from "../i18n/index";
|
||||
import { type Translator, createTranslator, defaultLocale } from "../i18n/index";
|
||||
import { localeService } from "../i18n/service";
|
||||
import { userService } from "./user";
|
||||
|
||||
|
|
@ -165,7 +165,13 @@ export const results = new Elysia()
|
|||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX-CN | Result" locale={locale}>
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn locale={locale} t={t} />
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
loggedIn
|
||||
locale={locale}
|
||||
t={t}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
|
|
|
|||
|
|
@ -17,237 +17,245 @@ import {
|
|||
import { localeService } from "../i18n/service";
|
||||
import { FIRST_RUN, userService } from "./user";
|
||||
|
||||
export const root = new Elysia().use(userService).use(localeService).get(
|
||||
"/",
|
||||
async ({ jwt, redirect, cookie: { auth, jobId }, locale, t }) => {
|
||||
if (!ALLOW_UNAUTHENTICATED) {
|
||||
if (FIRST_RUN) {
|
||||
return redirect(`${WEBROOT}/setup`, 302);
|
||||
export const root = new Elysia()
|
||||
.use(userService)
|
||||
.use(localeService)
|
||||
.get(
|
||||
"/",
|
||||
async ({ jwt, redirect, cookie: { auth, jobId }, locale, t }) => {
|
||||
if (!ALLOW_UNAUTHENTICATED) {
|
||||
if (FIRST_RUN) {
|
||||
return redirect(`${WEBROOT}/setup`, 302);
|
||||
}
|
||||
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
|
||||
if (!auth?.value) {
|
||||
// validate jwt
|
||||
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||
if (ALLOW_UNAUTHENTICATED) {
|
||||
const newUserId = String(
|
||||
UNAUTHENTICATED_USER_SHARING
|
||||
? 0
|
||||
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
||||
);
|
||||
const accessToken = await jwt.sign({
|
||||
id: newUserId,
|
||||
});
|
||||
|
||||
user = { id: newUserId };
|
||||
if (!auth) {
|
||||
return {
|
||||
message: t("auth", "noCookies"),
|
||||
};
|
||||
}
|
||||
|
||||
// set cookie
|
||||
auth.set({
|
||||
value: accessToken,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
} else if (auth?.value) {
|
||||
user = await jwt.verify(auth.value);
|
||||
|
||||
if (
|
||||
user !== false &&
|
||||
user.id &&
|
||||
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
||||
) {
|
||||
// Make sure user exists in db
|
||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
||||
|
||||
if (!existingUser) {
|
||||
if (auth?.value) {
|
||||
auth.remove();
|
||||
}
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
|
||||
// validate jwt
|
||||
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||
if (ALLOW_UNAUTHENTICATED) {
|
||||
const newUserId = String(
|
||||
UNAUTHENTICATED_USER_SHARING
|
||||
? 0
|
||||
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
||||
// create a new job
|
||||
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
||||
user.id,
|
||||
new Date().toISOString(),
|
||||
);
|
||||
const accessToken = await jwt.sign({
|
||||
id: newUserId,
|
||||
});
|
||||
|
||||
user = { id: newUserId };
|
||||
if (!auth) {
|
||||
return {
|
||||
message: t("auth", "noCookies"),
|
||||
};
|
||||
const { id } = db
|
||||
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
||||
.get(user.id) as { id: number };
|
||||
|
||||
if (!jobId) {
|
||||
return { message: t("auth", "cookiesRequired") };
|
||||
}
|
||||
|
||||
// set cookie
|
||||
auth.set({
|
||||
value: accessToken,
|
||||
jobId.set({
|
||||
value: id,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
} else if (auth?.value) {
|
||||
user = await jwt.verify(auth.value);
|
||||
|
||||
if (
|
||||
user !== false &&
|
||||
user.id &&
|
||||
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
||||
) {
|
||||
// Make sure user exists in db
|
||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
||||
console.log("jobId set to:", id);
|
||||
|
||||
if (!existingUser) {
|
||||
if (auth?.value) {
|
||||
auth.remove();
|
||||
}
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
// create a new job
|
||||
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
||||
user.id,
|
||||
new Date().toISOString(),
|
||||
);
|
||||
|
||||
const { id } = db
|
||||
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
||||
.get(user.id) as { id: number };
|
||||
|
||||
if (!jobId) {
|
||||
return { message: t("auth", "cookiesRequired") };
|
||||
}
|
||||
|
||||
jobId.set({
|
||||
value: id,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
console.log("jobId set to:", id);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} locale={locale}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
accountRegistration={ACCOUNT_REGISTRATION}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
locale={locale}
|
||||
t={t}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">{t("convert", "title")}</h1>
|
||||
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900
|
||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="dropzone"
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||
border-neutral-700 transition-all
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
<b>{t("convert", "chooseFile")}</b> {t("convert", "orDragHere")}
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
multiple
|
||||
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
<form
|
||||
method="post"
|
||||
action={`${WEBROOT}/convert`}
|
||||
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} locale={locale}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
accountRegistration={ACCOUNT_REGISTRATION}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
locale={locale}
|
||||
t={t}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article class="article w-full">
|
||||
<input
|
||||
type="search"
|
||||
name="convert_to_search"
|
||||
placeholder={t("convert", "searchConversions")}
|
||||
autocomplete="off"
|
||||
class="w-full rounded-sm bg-neutral-800 p-4"
|
||||
/>
|
||||
<div class="select_container relative">
|
||||
<article
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">{t("convert", "title")}</h1>
|
||||
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
w-full table-auto rounded bg-neutral-900
|
||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`
|
||||
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||
`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
||||
<select name="convert_to" aria-label={t("convert", "convertTo")} required hidden>
|
||||
<option selected disabled value="">
|
||||
{t("convert", "convertTo")}
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="dropzone"
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border
|
||||
border-dashed border-neutral-700 transition-all
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
<b>{t("convert", "chooseFile")}</b> {t("convert", "orDragHere")}
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
multiple
|
||||
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
<input
|
||||
class={`
|
||||
w-full btn-primary opacity-100
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
type="submit"
|
||||
value={t("convert", "convertButton")}
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
},
|
||||
{
|
||||
cookie: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
locale: t.Optional(t.String()),
|
||||
}),
|
||||
},
|
||||
);
|
||||
<form
|
||||
method="post"
|
||||
action={`${WEBROOT}/convert`}
|
||||
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
||||
>
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article class="article w-full">
|
||||
<input
|
||||
type="search"
|
||||
name="convert_to_search"
|
||||
placeholder={t("convert", "searchConversions")}
|
||||
autocomplete="off"
|
||||
class="w-full rounded-sm bg-neutral-800 p-4"
|
||||
/>
|
||||
<div class="select_container relative">
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full
|
||||
flex-col overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`
|
||||
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||
`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
||||
<select
|
||||
name="convert_to"
|
||||
aria-label={t("convert", "convertTo")}
|
||||
required
|
||||
hidden
|
||||
>
|
||||
<option selected disabled value="">
|
||||
{t("convert", "convertTo")}
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</article>
|
||||
<input
|
||||
class={`
|
||||
w-full btn-primary opacity-100
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
type="submit"
|
||||
value={t("convert", "convertButton")}
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
},
|
||||
{
|
||||
cookie: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
locale: t.Optional(t.String()),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -173,7 +173,11 @@ export const user = new Elysia()
|
|||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<input type="submit" value={t("auth", "registerButton")} class="w-full btn-primary" />
|
||||
<input
|
||||
type="submit"
|
||||
value={t("auth", "registerButton")}
|
||||
class="w-full btn-primary"
|
||||
/>
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
|
|
@ -302,7 +306,11 @@ export const user = new Elysia()
|
|||
>
|
||||
{t("nav", "register")}
|
||||
</a>
|
||||
<input type="submit" value={t("auth", "loginButton")} class="w-full btn-primary" />
|
||||
<input
|
||||
type="submit"
|
||||
value={t("auth", "loginButton")}
|
||||
class="w-full btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
|
@ -441,7 +449,11 @@ export const user = new Elysia()
|
|||
</label>
|
||||
</fieldset>
|
||||
<div role="group">
|
||||
<input type="submit" value={t("auth", "updateButton")} class="w-full btn-primary" />
|
||||
<input
|
||||
type="submit"
|
||||
value={t("auth", "updateButton")}
|
||||
class={`w-full btn-primary`}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue