D2-A: the library row's Share button becomes a single action — quickShareLink() ensures a public link (minting on demand), copies it to the clipboard, and toasts with an "Open in new tab" action. A copy failure still toasts (warning) with that action so the link is never a dead end. Advanced controls (password/expiry/revoke/ user-ACL) stay in the existing ShareDialog, reached via a new "Manage sharing" row button. D1 (forward-only): CreateLinkIn.allow_download defaults to true, so a newly minted link is downloadable by default. The default lives in the API, not a migration — the download_links column default stays false, so no existing link flips. The ShareDialog create-default is flipped to match. Tests: TestCreateLinkDefault (default true; explicit false honored); 4 vitest cases for quickShareLink (mint-vs-reuse, copy ok/fail, create failure). HU/EN parity.
53 lines
2.2 KiB
TypeScript
53 lines
2.2 KiB
TypeScript
import type { QueryClient } from "@tanstack/react-query";
|
|
import { qk } from "./queryKeys";
|
|
import { api } from "./api";
|
|
import { copyText } from "./clipboard";
|
|
import { notify } from "./notifications";
|
|
|
|
// The download queue, the per-card download index, and the storage-usage meter all move together
|
|
// whenever a job is enqueued, deleted, or its state changes — invalidate them as one unit.
|
|
export function invalidateDownloads(qc: QueryClient) {
|
|
qc.invalidateQueries({ queryKey: qk.downloads() });
|
|
qc.invalidateQueries({ queryKey: qk.downloadIndex() });
|
|
qc.invalidateQueries({ queryKey: qk.downloadUsage() });
|
|
}
|
|
|
|
// One-click share (E7 S3 · D2-A): the row's Share button turns "share this" into a single action.
|
|
// Ensure the job has a public link (minting one on demand — omitting `allow_download` so the backend
|
|
// D1 default makes it downloadable), copy it to the clipboard, and toast with an "open in new tab"
|
|
// action. A copy failure still toasts (warning) WITH that action, so the link is never a dead end.
|
|
// Advanced controls (password, expiry, revoke, share-with-a-user) live in the Share dialog. Returns
|
|
// the shared URL, or null if link creation failed — so the caller/tests can react to the outcome.
|
|
export async function quickShareLink(
|
|
qc: QueryClient,
|
|
job: { id: number },
|
|
t: (key: string) => string,
|
|
): Promise<string | null> {
|
|
let link;
|
|
try {
|
|
const existing = await qc.fetchQuery({
|
|
queryKey: qk.downloadLinks(job.id),
|
|
queryFn: () => api.downloadLinks(job.id),
|
|
});
|
|
link = existing[0];
|
|
if (!link) {
|
|
link = await api.createDownloadLink(job.id, {}); // omit allow_download → backend D1 default (true)
|
|
qc.setQueryData(qk.downloadLinks(job.id), [link]);
|
|
}
|
|
} catch {
|
|
notify({ level: "error", message: t("downloads.share.linkFailed") });
|
|
return null;
|
|
}
|
|
const url = window.location.origin + link.url;
|
|
const action = {
|
|
label: t("downloads.share.openNewTab"),
|
|
onClick: () => window.open(url, "_blank", "noopener,noreferrer"),
|
|
};
|
|
notify(
|
|
(await copyText(url))
|
|
? { level: "success", message: t("downloads.share.linkCopied"), action }
|
|
: { level: "warning", message: t("downloads.share.copyFailed"), action },
|
|
);
|
|
return url;
|
|
}
|