Merge pull request 'terry-staging' (#4) from terry-staging into main
All checks were successful
Deploy to Frontend Servers / deploy (push) Successful in 19s
All checks were successful
Deploy to Frontend Servers / deploy (push) Successful in 19s
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -27,11 +27,19 @@ export function MessageBubble({ post }: { post: Post }) {
|
|||||||
const { lang } = useI18n();
|
const { lang } = useI18n();
|
||||||
const Bubble = pickBubble(post);
|
const Bubble = pickBubble(post);
|
||||||
const isTextOnly = post.attachments.length === 0;
|
const isTextOnly = post.attachments.length === 0;
|
||||||
|
const isVisual = post.attachments.some(
|
||||||
|
(a) => a.kind === "image" || a.kind === "video",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<div
|
||||||
id={`post-${post.id}`}
|
id={`post-${post.id}`}
|
||||||
className={`relative mx-auto w-full max-w-[380px] rounded-2xl bg-ark-panel text-left shadow-sm md:max-w-[680px] lg:max-w-[900px] xl:max-w-[1120px] ${isTextOnly ? "px-3 py-2" : "p-2"}`}
|
className="mx-auto w-full max-w-[380px] md:max-w-[680px] lg:max-w-[900px] xl:max-w-[1120px]"
|
||||||
|
>
|
||||||
|
<article
|
||||||
|
className={`relative rounded-2xl bg-ark-panel text-left shadow-sm ${
|
||||||
|
isVisual ? "w-full" : "w-fit max-w-full"
|
||||||
|
} ${isTextOnly ? "px-3 py-2" : "p-2"}`}
|
||||||
>
|
>
|
||||||
<Bubble post={post} />
|
<Bubble post={post} />
|
||||||
<time
|
<time
|
||||||
@@ -42,5 +50,6 @@ export function MessageBubble({ post }: { post: Post }) {
|
|||||||
</time>
|
</time>
|
||||||
<span className="block clear-both" />
|
<span className="block clear-both" />
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,5 @@
|
|||||||
import { assetUrl } from "../../../api";
|
import { assetUrl } from "../../../api";
|
||||||
|
|
||||||
type SaveFilePicker = (options?: {
|
|
||||||
suggestedName?: string;
|
|
||||||
types?: Array<{
|
|
||||||
description?: string;
|
|
||||||
accept: Record<string, string[]>;
|
|
||||||
}>;
|
|
||||||
}) => Promise<{
|
|
||||||
createWritable: () => Promise<{
|
|
||||||
write: (data: Blob) => Promise<void>;
|
|
||||||
close: () => Promise<void>;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
type NavigatorWithFileShare = Navigator & {
|
|
||||||
canShare?: (data: { files?: File[] }) => boolean;
|
|
||||||
share?: (data: { files?: File[]; title?: string }) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type WindowWithSavePicker = Window & {
|
|
||||||
showSaveFilePicker?: SaveFilePicker;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function attachmentDownloadUrl(postId: string, attachmentId: string) {
|
export function attachmentDownloadUrl(postId: string, attachmentId: string) {
|
||||||
return assetUrl(
|
return assetUrl(
|
||||||
`/api/posts/${encodeURIComponent(postId)}/attachments/${encodeURIComponent(
|
`/api/posts/${encodeURIComponent(postId)}/attachments/${encodeURIComponent(
|
||||||
@@ -39,45 +17,7 @@ export async function downloadAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadFile(url: string, filename: string) {
|
export async function downloadFile(url: string, filename: string) {
|
||||||
const res = await fetch(url, { credentials: "include" });
|
triggerDownload(url, filename || "download");
|
||||||
if (!res.ok) throw new Error(await res.text());
|
|
||||||
|
|
||||||
const blob = await res.blob();
|
|
||||||
const safeName = filename || "download";
|
|
||||||
|
|
||||||
if (window.isSecureContext) {
|
|
||||||
const picker = (window as WindowWithSavePicker).showSaveFilePicker;
|
|
||||||
if (picker) {
|
|
||||||
const handle = await picker({
|
|
||||||
suggestedName: safeName,
|
|
||||||
types: blob.type
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
description: "File",
|
|
||||||
accept: { [blob.type]: [extensionFromName(safeName)] },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
const writable = await handle.createWritable();
|
|
||||||
await writable.write(blob);
|
|
||||||
await writable.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = new File([blob], safeName, {
|
|
||||||
type: blob.type || "application/octet-stream",
|
|
||||||
});
|
|
||||||
const nav = navigator as NavigatorWithFileShare;
|
|
||||||
if (nav.canShare?.({ files: [file] }) && nav.share) {
|
|
||||||
await nav.share({ files: [file], title: safeName });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectUrl = URL.createObjectURL(blob);
|
|
||||||
triggerDownload(objectUrl, safeName);
|
|
||||||
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerDownload(url: string, filename: string) {
|
function triggerDownload(url: string, filename: string) {
|
||||||
@@ -89,8 +29,3 @@ function triggerDownload(url: string, filename: string) {
|
|||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function extensionFromName(filename: string) {
|
|
||||||
const match = /\.[^.]+$/.exec(filename);
|
|
||||||
return match?.[0] || ".bin";
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user