All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account into a single repository. Eliminates submodule sync, sibling model copying at build time, and per-app CI orchestration. Changes: - Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs - Remove stale sibling model copies from each app - Update all 6 Dockerfiles for monorepo build context (root = .) - Add build directives to docker-compose.yml - Add single .gitea/workflows/ci.yml with change detection - Add .dockerignore for monorepo build context - Create __init__.py for federation and account (cross-app imports)
100 lines
3.6 KiB
JavaScript
100 lines
3.6 KiB
JavaScript
import { useState, useCallback, useRef } from "react";
|
|
|
|
/**
|
|
* Koenig expects `fileUploader.useFileUpload(type)` — a React hook it
|
|
* calls internally for each card type ("image", "audio", "file", etc.).
|
|
*
|
|
* `makeFileUploader(csrfToken, uploadUrls)` returns the object Koenig wants:
|
|
* { useFileUpload: (type) => { upload, progress, isLoading, errors, filesNumber } }
|
|
*
|
|
* `uploadUrls` is an object: { image, media, file }
|
|
* For backwards compat, a plain string is treated as the image URL.
|
|
*/
|
|
|
|
const URL_KEY_MAP = {
|
|
image: { urlKey: "image", responseKey: "images" },
|
|
audio: { urlKey: "media", responseKey: "media" },
|
|
video: { urlKey: "media", responseKey: "media" },
|
|
mediaThumbnail: { urlKey: "image", responseKey: "images" },
|
|
file: { urlKey: "file", responseKey: "files" },
|
|
};
|
|
|
|
export default function makeFileUploader(csrfToken, uploadUrls) {
|
|
// Normalise: string → object with all keys pointing to same URL
|
|
const urls =
|
|
typeof uploadUrls === "string"
|
|
? { image: uploadUrls, media: uploadUrls, file: uploadUrls }
|
|
: uploadUrls || {};
|
|
|
|
return {
|
|
fileTypes: {
|
|
image: { mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'] },
|
|
audio: { mimeTypes: ['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/mp4', 'audio/aac'] },
|
|
video: { mimeTypes: ['video/mp4', 'video/webm', 'video/ogg'] },
|
|
mediaThumbnail: { mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] },
|
|
file: { mimeTypes: [] },
|
|
},
|
|
useFileUpload(type) {
|
|
const mapping = URL_KEY_MAP[type] || URL_KEY_MAP.image;
|
|
const [progress, setProgress] = useState(0);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [errors, setErrors] = useState([]);
|
|
const [filesNumber, setFilesNumber] = useState(0);
|
|
const csrfRef = useRef(csrfToken);
|
|
const urlRef = useRef(urls[mapping.urlKey] || urls.image || "/editor-api/images/upload/");
|
|
const responseKeyRef = useRef(mapping.responseKey);
|
|
|
|
const upload = useCallback(async (files) => {
|
|
const fileList = Array.from(files);
|
|
setFilesNumber(fileList.length);
|
|
setIsLoading(true);
|
|
setErrors([]);
|
|
setProgress(0);
|
|
|
|
const results = [];
|
|
for (let i = 0; i < fileList.length; i++) {
|
|
const file = fileList[i];
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
|
|
try {
|
|
const resp = await fetch(urlRef.current, {
|
|
method: "POST",
|
|
body: formData,
|
|
headers: {
|
|
"X-CSRFToken": csrfRef.current || "",
|
|
},
|
|
});
|
|
if (!resp.ok) {
|
|
const err = await resp.json().catch(() => ({}));
|
|
const msg =
|
|
err.errors?.[0]?.message || `Upload failed (${resp.status})`;
|
|
setErrors((prev) => [
|
|
...prev,
|
|
{ message: msg, fileName: file.name },
|
|
]);
|
|
continue;
|
|
}
|
|
const data = await resp.json();
|
|
const fileUrl = data[responseKeyRef.current]?.[0]?.url;
|
|
if (fileUrl) {
|
|
results.push({ url: fileUrl, fileName: file.name });
|
|
}
|
|
} catch (e) {
|
|
setErrors((prev) => [
|
|
...prev,
|
|
{ message: e.message, fileName: file.name },
|
|
]);
|
|
}
|
|
setProgress(Math.round(((i + 1) / fileList.length) * 100));
|
|
}
|
|
|
|
setIsLoading(false);
|
|
return results;
|
|
}, []);
|
|
|
|
return { upload, progress, isLoading, errors, filesNumber };
|
|
},
|
|
};
|
|
}
|