fix: allow js/css/txt extensions and limit upload concurrency to 6
This commit is contained in:
parent
c6cc9b7146
commit
7386b215e2
@ -11,7 +11,12 @@ logger = logging.getLogger(__name__)
|
|||||||
def _natural_sort_key(name: str) -> list:
|
def _natural_sort_key(name: str) -> list:
|
||||||
return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)]
|
return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)]
|
||||||
|
|
||||||
ALLOWED_UPLOAD_EXTENSIONS = {".md", ".html", ".htm", ".png", ".jpg", ".jpeg", ".gif", ".webp"}
|
ALLOWED_UPLOAD_EXTENSIONS = {
|
||||||
|
".md", ".html", ".htm",
|
||||||
|
".js", ".ts", ".css",
|
||||||
|
".json", ".yaml", ".yml", ".toml", ".xml", ".csv", ".txt",
|
||||||
|
".png", ".jpg", ".jpeg", ".gif", ".webp",
|
||||||
|
}
|
||||||
MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024
|
MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,37 @@ interface UploadButtonProps {
|
|||||||
onUploaded: (result: UploadDocumentResponse) => void;
|
onUploaded: (result: UploadDocumentResponse) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONCURRENCY = 6;
|
||||||
|
|
||||||
|
async function uploadQueue(
|
||||||
|
entries: FileWithRelativePath[],
|
||||||
|
path: string,
|
||||||
|
onDone: (result: UploadDocumentResponse) => void,
|
||||||
|
onError: (msg: string) => void,
|
||||||
|
) {
|
||||||
|
let i = 0;
|
||||||
|
async function next() {
|
||||||
|
if (i >= entries.length) return;
|
||||||
|
const entry = entries[i++];
|
||||||
|
const name = entry.relativePath ?? entry.file.name;
|
||||||
|
try {
|
||||||
|
const result = await (entry.relativePath
|
||||||
|
? uploadDocument(entry.file, path, entry.relativePath)
|
||||||
|
: uploadDocument(entry.file, path));
|
||||||
|
onDone(result);
|
||||||
|
} catch (err) {
|
||||||
|
onError(`${name}:${err instanceof Error ? err.message : '上传失败'}`);
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, entries.length) }, next));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadButtonProps {
|
||||||
|
path: string;
|
||||||
|
onUploaded: (result: UploadDocumentResponse) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
|
export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
@ -16,52 +47,29 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
|
|||||||
|
|
||||||
function setFolderInputRef(node: HTMLInputElement | null) {
|
function setFolderInputRef(node: HTMLInputElement | null) {
|
||||||
folderInputRef.current = node;
|
folderInputRef.current = node;
|
||||||
if (node) {
|
if (node) node.setAttribute('webkitdirectory', '');
|
||||||
node.setAttribute('webkitdirectory', '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openModal() {
|
function openModal() { setIsOpen(true); }
|
||||||
setIsOpen(true);
|
function closeModal() { setIsOpen(false); setIsDragOver(false); setUploadError(null); }
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
function runQueue(entries: FileWithRelativePath[]) {
|
||||||
setIsOpen(false);
|
|
||||||
setIsDragOver(false);
|
|
||||||
setUploadError(null);
|
setUploadError(null);
|
||||||
}
|
uploadQueue(entries, path, onUploaded, setUploadError);
|
||||||
|
|
||||||
function uploadOne(entry: FileWithRelativePath) {
|
|
||||||
setUploadError(null);
|
|
||||||
const promise = entry.relativePath
|
|
||||||
? uploadDocument(entry.file, path, entry.relativePath)
|
|
||||||
: uploadDocument(entry.file, path);
|
|
||||||
promise
|
|
||||||
.then((result) => {
|
|
||||||
onUploaded(result);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setUploadError(err instanceof Error ? err.message : '上传失败');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadFiles(files: FileList | File[]) {
|
|
||||||
Array.from(files).forEach((file) => uploadOne({ file }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleFileInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
if (event.target.files) {
|
if (event.target.files)
|
||||||
uploadFiles(event.target.files);
|
runQueue(Array.from(event.target.files).map((file) => ({ file })));
|
||||||
}
|
|
||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFolderInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleFolderInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
if (event.target.files) {
|
if (event.target.files)
|
||||||
Array.from(event.target.files).forEach((file) =>
|
runQueue(Array.from(event.target.files).map((file) => ({
|
||||||
uploadOne({ file, relativePath: file.webkitRelativePath || file.name }),
|
file,
|
||||||
);
|
relativePath: file.webkitRelativePath || file.name,
|
||||||
}
|
})));
|
||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,14 +77,12 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setIsDragOver(false);
|
setIsDragOver(false);
|
||||||
const items = event.dataTransfer.items;
|
const items = event.dataTransfer.items;
|
||||||
if (items && items.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') {
|
if (items?.length > 0 && typeof items[0]?.webkitGetAsEntry === 'function') {
|
||||||
const entries = await readFileEntries(items);
|
runQueue(await readFileEntries(items));
|
||||||
entries.forEach(uploadOne);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.dataTransfer.files) {
|
if (event.dataTransfer.files)
|
||||||
uploadFiles(event.dataTransfer.files);
|
runQueue(Array.from(event.dataTransfer.files).map((file) => ({ file })));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragOver(event: React.DragEvent<HTMLDivElement>) {
|
function handleDragOver(event: React.DragEvent<HTMLDivElement>) {
|
||||||
@ -84,9 +90,7 @@ export default function UploadButton({ path, onUploaded }: UploadButtonProps) {
|
|||||||
setIsDragOver(true);
|
setIsDragOver(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragLeave() {
|
function handleDragLeave() { setIsDragOver(false); }
|
||||||
setIsDragOver(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user