Use backend endpoint for attachment downloads

This commit is contained in:
TerryM
2026-05-27 12:17:47 +08:00
parent 68cbce9cf1
commit 9453777dba
5 changed files with 42 additions and 23 deletions

View File

@@ -1,12 +1,15 @@
import { Download } from "lucide-react"; import { Download } from "lucide-react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import type { Resource } from "../api"; import type { Resource } from "../api";
import { assetUrl, postJSON, postNoBody } from "../api"; import { assetUrl, postJSON } from "../api";
import { useI18n } from "../i18n"; import { useI18n } from "../i18n";
import { useMemo } from "react"; import { useMemo } from "react";
import { formatDateYmd } from "../utils/format"; import { formatDateYmd } from "../utils/format";
import { officialRecommendationCoverFallbacks } from "./FigmaBanner"; import { officialRecommendationCoverFallbacks } from "./FigmaBanner";
import { downloadFile } from "./messageStream/utils/downloadFile"; import {
downloadAttachment,
downloadFile,
} from "./messageStream/utils/downloadFile";
function isPlaceholderAsset(path: string | undefined | null) { function isPlaceholderAsset(path: string | undefined | null) {
return !path || path.includes("placeholder-cover"); return !path || path.includes("placeholder-cover");
@@ -88,14 +91,16 @@ export function RecommendedCard({
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (r.downloadPostId && r.downloadAttachmentId) {
void downloadAttachment(
r.downloadPostId,
r.downloadAttachmentId,
r.title,
).catch(() => {});
return;
}
try { try {
if (r.downloadPostId && r.downloadAttachmentId) { await postJSON(`/api/resources/${r.id}/download`, {});
await postNoBody(
`/api/posts/${r.downloadPostId}/attachments/${r.downloadAttachmentId}/download`,
);
} else {
await postJSON(`/api/resources/${r.id}/download`, {});
}
} catch { } catch {
/* ignore */ /* ignore */
} }

View File

@@ -1,8 +1,7 @@
import { ArrowDownToLine } from "lucide-react"; import { ArrowDownToLine } from "lucide-react";
import { postNoBody } from "../../../api";
import { useI18n } from "../../../i18n"; import { useI18n } from "../../../i18n";
import type { Attachment, Post } from "../../../types/post"; import type { Attachment, Post } from "../../../types/post";
import { downloadFile } from "../utils/downloadFile"; import { downloadAttachment } from "../utils/downloadFile";
import { fileIcon } from "../utils/fileIcon"; import { fileIcon } from "../utils/fileIcon";
import { import {
filenameWithExtension, filenameWithExtension,
@@ -17,8 +16,7 @@ function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
const displayFilename = filenameWithExtension(att.filename, att.mime); const displayFilename = filenameWithExtension(att.filename, att.mime);
const handleDownload = () => { const handleDownload = () => {
void postNoBody(`/api/posts/${postId}/attachments/${att.id}/download`); void downloadAttachment(postId, att.id, displayFilename).catch(() => {});
void downloadFile(att.url, displayFilename).catch(() => {});
}; };
return ( return (

View File

@@ -1,11 +1,10 @@
import { ArrowDownToLine, Play } from "lucide-react"; import { ArrowDownToLine, Play } from "lucide-react";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { postNoBody } from "../../../api";
import { useI18n } from "../../../i18n"; import { useI18n } from "../../../i18n";
import type { Post } from "../../../types/post"; import type { Post } from "../../../types/post";
import { useVideoPlayer } from "../overlays/VideoPlayer"; import { useVideoPlayer } from "../overlays/VideoPlayer";
import { autolink } from "../utils/autolink"; import { autolink } from "../utils/autolink";
import { downloadFile } from "../utils/downloadFile"; import { downloadAttachment } from "../utils/downloadFile";
import { formatBytes } from "../utils/formatBytes"; import { formatBytes } from "../utils/formatBytes";
import { postDisplayText } from "../utils/postText"; import { postDisplayText } from "../utils/postText";
@@ -74,10 +73,9 @@ export function VideoBubble({ post }: { post: Post }) {
type="button" type="button"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
void postNoBody( void downloadAttachment(post.id, att.id, att.filename).catch(
`/api/posts/${post.id}/attachments/${att.id}/download`, () => {},
); );
void downloadFile(att.url, att.filename).catch(() => {});
}} }}
className="group absolute left-3 top-3 z-10 inline-flex overflow-hidden rounded-full bg-black/45 text-xs text-white shadow-lg ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/60" className="group absolute left-3 top-3 z-10 inline-flex overflow-hidden rounded-full bg-black/45 text-xs text-white shadow-lg ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/60"
aria-label={`Download ${att.filename}`} aria-label={`Download ${att.filename}`}

View File

@@ -9,10 +9,9 @@ import {
} from "react"; } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { ChevronLeft, ChevronRight, Download, X } from "lucide-react"; import { ChevronLeft, ChevronRight, Download, X } from "lucide-react";
import { postNoBody } from "../../../api";
import type { Attachment } from "../../../types/post"; import type { Attachment } from "../../../types/post";
import { autolink } from "../utils/autolink"; import { autolink } from "../utils/autolink";
import { downloadFile } from "../utils/downloadFile"; import { downloadAttachment, downloadFile } from "../utils/downloadFile";
type LightboxState = { type LightboxState = {
images: Attachment[]; images: Attachment[];
@@ -143,11 +142,12 @@ function LightboxView({
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (postId) { if (postId) {
void postNoBody( void downloadAttachment(postId, current.id, current.filename).catch(
`/api/posts/${postId}/attachments/${current.id}/download`, () => {},
); );
} else {
void downloadFile(current.url, current.filename).catch(() => {});
} }
void downloadFile(current.url, current.filename).catch(() => {});
}} }}
className="absolute right-16 top-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20" className="absolute right-16 top-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20"
aria-label="Download" aria-label="Download"

View File

@@ -1,3 +1,5 @@
import { assetUrl } from "../../../api";
type SaveFilePicker = (options?: { type SaveFilePicker = (options?: {
suggestedName?: string; suggestedName?: string;
types?: Array<{ types?: Array<{
@@ -20,6 +22,22 @@ type WindowWithSavePicker = Window & {
showSaveFilePicker?: SaveFilePicker; showSaveFilePicker?: SaveFilePicker;
}; };
export function attachmentDownloadUrl(postId: string, attachmentId: string) {
return assetUrl(
`/api/posts/${encodeURIComponent(postId)}/attachments/${encodeURIComponent(
attachmentId,
)}/download`,
);
}
export async function downloadAttachment(
postId: string,
attachmentId: string,
filename: string,
) {
return downloadFile(attachmentDownloadUrl(postId, attachmentId), filename);
}
export async function downloadFile(url: string, filename: string) { export async function downloadFile(url: string, filename: string) {
const res = await fetch(url, { credentials: "include" }); const res = await fetch(url, { credentials: "include" });
if (!res.ok) throw new Error(await res.text()); if (!res.ok) throw new Error(await res.text());