This commit is contained in:
Florian 2026-08-28 06:13:43 +02:00 committed by GitHub
commit 02660c5678
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 161 additions and 17 deletions

View file

@ -16,7 +16,7 @@
import { onMount, tick, getContext, createEventDispatcher } from 'svelte'; import { onMount, tick, getContext, createEventDispatcher } from 'svelte';
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker'; import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker'; import { pickAndDownloadFiles } from '$lib/utils/onedrive-file-picker';
import { KokoroWorker } from '$lib/workers/KokoroWorker'; import { KokoroWorker } from '$lib/workers/KokoroWorker';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -2216,17 +2216,47 @@
}} }}
uploadOneDriveHandler={async (authorityType) => { uploadOneDriveHandler={async (authorityType) => {
try { try {
const fileData = await pickAndDownloadFile(authorityType); const maxFileCount = $config?.file?.max_count;
if (fileData) { const filesData = await pickAndDownloadFiles(
const file = new File([fileData.blob], fileData.name, { authorityType,
type: fileData.blob.type || 'application/octet-stream' maxFileCount
}); );
await uploadFileHandler(file); if (filesData && filesData.length > 0) {
if (filesData.length > 1) {
toast.success(
$i18n.t('Uploading {{count}} files from OneDrive...', {
count: filesData.length
})
);
}
for (const fileData of filesData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
}
} else { } else {
console.log('No file was selected from OneDrive'); console.log('No files were selected from OneDrive');
} }
} catch (error) { } catch (error) {
console.error('OneDrive Error:', error); console.error('OneDrive Error:', error);
// Handle specific max file count error
if (error.message?.startsWith('MAX_FILE_COUNT_EXCEEDED:')) {
const [, count, max] = error.message.split(':');
toast.error(
$i18n.t('Selected items contain {{count}} files, but maximum is {{max}}.', {
count,
max
})
);
} else {
toast.error(
$i18n.t('OneDrive Error: {{error}}', {
error: error.message
})
);
}
} }
}} }}
{onUpload} {onUpload}

View file

@ -180,6 +180,9 @@ interface PickerParams {
search: { search: {
enabled: boolean; enabled: boolean;
}; };
selection?: {
mode?: 'single' | 'multiple' | 'pick';
};
typesAndSources: { typesAndSources: {
mode: string; mode: string;
pivots: Record<string, boolean>; pivots: Record<string, boolean>;
@ -211,12 +214,18 @@ function getPickerParams(): PickerParams {
search: { search: {
enabled: true enabled: true
}, },
selection: {
mode: 'multiple'
},
typesAndSources: { typesAndSources: {
mode: 'files', mode: 'all',
pivots: { pivots: {
oneDrive: true, oneDrive: true,
recent: true, recent: true,
myOrganization: config.getAuthorityType() === 'organizations' shared: true,
sharedLibraries: true,
myOrganization: config.getAuthorityType() === 'organizations',
site: config.getAuthorityType() === 'organizations'
} }
} }
}; };
@ -236,10 +245,66 @@ interface OneDriveFileInfo {
driveId: string; driveId: string;
}; };
'@sharePoint.endpoint': string; '@sharePoint.endpoint': string;
folder?: {
childCount?: number;
};
file?: {
mimeType?: string;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; [key: string]: any;
} }
// List all items in a folder recursively
async function listFolderContentsRecursive(
folderInfo: OneDriveFileInfo,
authorityType?: 'personal' | 'organizations',
currentPath: string = ''
): Promise<OneDriveFileInfo[]> {
const accessToken = await getToken(undefined, authorityType);
if (!accessToken) {
throw new Error('Unable to retrieve OneDrive access token.');
}
const childrenUrl = `${folderInfo['@sharePoint.endpoint']}/drives/${folderInfo.parentReference.driveId}/items/${folderInfo.id}/children`;
const response = await fetch(childrenUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`Failed to list folder contents: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const items: OneDriveFileInfo[] = data.value || [];
const allFiles: OneDriveFileInfo[] = [];
// Process all items
for (const item of items) {
// Inherit the @sharePoint.endpoint from parent folder if not present
if (!item['@sharePoint.endpoint']) {
item['@sharePoint.endpoint'] = folderInfo['@sharePoint.endpoint'];
}
if (item.folder) {
// Recursively get files from subfolder
const subPath = currentPath ? `${currentPath}/${item.name}` : item.name;
const subfolderFiles = await listFolderContentsRecursive(item, authorityType, subPath);
allFiles.push(...subfolderFiles);
} else if (item.file) {
// It's a file, add it to the list
allFiles.push(item);
}
// Skip items that are neither files nor folders (e.g., packages, OneNote notebooks)
}
return allFiles;
}
// Download file from OneDrive // Download file from OneDrive
async function downloadOneDriveFile( async function downloadOneDriveFile(
fileInfo: OneDriveFileInfo, fileInfo: OneDriveFileInfo,
@ -435,20 +500,69 @@ export async function openOneDrivePicker(
}); });
} }
// Pick and download file from OneDrive // Pick and download multiple files from OneDrive (with folder support)
export async function pickAndDownloadFile( export async function pickAndDownloadFiles(
authorityType?: 'personal' | 'organizations' authorityType?: 'personal' | 'organizations',
): Promise<{ blob: Blob; name: string } | null> { maxFileCount?: number
): Promise<{ blob: Blob; name: string }[] | null> {
const pickerResult = await openOneDrivePicker(authorityType); const pickerResult = await openOneDrivePicker(authorityType);
if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) { if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) {
return null; return null;
} }
const selectedFile = pickerResult.items[0]; const allFiles: OneDriveFileInfo[] = [];
const blob = await downloadOneDriveFile(selectedFile, authorityType);
return { blob, name: selectedFile.name }; // First, expand folders to get all files
for (const item of pickerResult.items) {
if (item.folder) {
// It's a folder, get all files recursively
const folderFiles = await listFolderContentsRecursive(item, authorityType, item.name);
allFiles.push(...folderFiles);
} else {
// It's a file
allFiles.push(item);
}
}
// Check if any files were actually found
if (allFiles.length === 0) {
throw new Error('No files found in the selected items.');
}
// Check if the number of files exceeds the configured maximum
if (maxFileCount && allFiles.length > maxFileCount) {
throw new Error(
`MAX_FILE_COUNT_EXCEEDED:${allFiles.length}:${maxFileCount}`
);
}
// Download all files with error handling
const downloadPromises = allFiles.map(async (fileInfo) => {
const blob = await downloadOneDriveFile(fileInfo, authorityType);
return {
blob,
name: fileInfo.name
};
});
const results = await Promise.allSettled(downloadPromises);
const successful = results
.filter((r): r is PromiseFulfilledResult<{ blob: Blob; name: string }> => r.status === 'fulfilled')
.map((r) => r.value);
const failed = results.filter((r) => r.status === 'rejected');
if (failed.length > 0) {
console.warn(`Failed to download ${failed.length} out of ${allFiles.length} files from OneDrive`);
// If all downloads failed, throw error
if (successful.length === 0) {
throw new Error('Failed to download any files from OneDrive');
}
// If some succeeded, log warning but continue with successful ones
}
return successful.length > 0 ? successful : null;
} }
export { downloadOneDriveFile }; export { downloadOneDriveFile };