feat(downloads): make a GIF in the clip panel and the editor, and show a shared one as itself

The clip panel and the editor get a Video / GIF choice with the GIF's size, the size it is expected
to come out at, and what that size means (no moving link preview over 15 MB, refused over 30 MB).
A range longer than a GIF may be leaves Make unavailable with its reason. The editor makes one GIF
per kept segment; Join says why it is unavailable instead of going dead. The share page shows a
picture as itself with its Download, and a picture in a folder opens above the list.
This commit is contained in:
2026-11-11 02:10:07 +01:00
parent c221452a00
commit 27ccf253f5
15 changed files with 775 additions and 37 deletions
+64
View File
@@ -161,6 +161,46 @@ describe("WatchPage media element", () => {
expect(dl!.className).toContain("state-filled");
});
it("shows a shared picture as itself, no player, with its kind, size and the chat tip", async () => {
window.history.replaceState({}, "", "/watch/tok-gif");
mockMeta({
needs_password: false,
media_kind: "file",
preview: "image",
title: "Evening clip",
container: "gif",
width: 480,
height: 270,
size_bytes: 6_500_000,
allow_download: true,
file_url: "/f.gif",
});
const page = render(<WatchPage />);
const img = await page.findByRole("img", { name: "Evening clip" });
expect(img.getAttribute("src")).toBe("/f.gif");
expect(page.container.querySelector("video")).toBeNull();
expect(page.getByText(/GIF · 480×270 · 6\.2 MB/)).toBeTruthy();
expect(page.getByRole("link", { name: "Download" }).getAttribute("href")).toBe("/f.gif");
expect(page.getByText(/drag the downloaded file in/)).toBeTruthy();
});
it("a view-only picture link shows the picture, without Download or the chat tip", async () => {
window.history.replaceState({}, "", "/watch/tok-gif-view");
mockMeta({
needs_password: false,
media_kind: "file",
preview: "image",
title: "Still",
container: "gif",
allow_download: false,
file_url: "/f.gif",
});
const page = render(<WatchPage />);
await page.findByRole("img", { name: "Still" });
expect(page.queryByRole("link", { name: "Download" })).toBeNull();
expect(page.queryByText(/drag the downloaded file in/)).toBeNull();
});
it("omits the Download for a view-only file link", async () => {
window.history.replaceState({}, "", "/watch/tok-file-noview");
mockMeta({
@@ -418,6 +458,30 @@ describe("WatchPage folder", () => {
);
});
it("opens a picture above the list, shown rather than played", async () => {
window.history.replaceState({}, "", "/watch/tok-folder");
mockMeta(
folder({
files: [
file(1),
file(4, {
title: "Wave.gif",
media_kind: "file",
preview: "image",
container: "gif",
duration_s: null,
}),
],
}),
);
const page = render(<WatchPage />);
fireEvent.click(await page.findByRole("button", { name: "Open: Wave.gif" }));
const img = await page.findByRole("img", { name: "Wave.gif" });
expect(img.getAttribute("src")).toBe("/api/public/watch/tok-folder/files/4/file");
expect(page.container.querySelector("video")).toBeNull();
expect(page.getByRole("heading", { level: 2 }).textContent).toBe("Wave.gif");
});
it("offers a document for download only, and Download only where the link allows it", async () => {
window.history.replaceState({}, "", "/watch/tok-folder");
mockMeta(folder());