Make folders selectable and downloadable with item count and sharing status.
Deploy Ladill Transfer / deploy (push) Successful in 35s
Deploy Ladill Transfer / deploy (push) Successful in 35s
Folder rows match the file table layout, support zip download, and participate in bulk selection, share, and copy-link actions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+76
-20
@@ -510,9 +510,12 @@ Alpine.data('transferCreateForm', (config = {}) => ({
|
||||
}));
|
||||
|
||||
Alpine.data('filesManager', (config = {}) => ({
|
||||
selected: [],
|
||||
selectedFileIds: [],
|
||||
selectedFolderIds: [],
|
||||
fileIds: config.fileIds || [],
|
||||
files: config.files || [],
|
||||
folderIds: config.folderIds || [],
|
||||
folders: config.folders || [],
|
||||
routes: config.routes || {},
|
||||
csrf: config.csrf || '',
|
||||
currentFolderId: config.currentFolderId || null,
|
||||
@@ -520,20 +523,38 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
toast: '',
|
||||
_toastTimer: null,
|
||||
|
||||
get selectedCount() {
|
||||
return this.selectedFileIds.length + this.selectedFolderIds.length;
|
||||
},
|
||||
|
||||
get allSelected() {
|
||||
return this.fileIds.length > 0 && this.selected.length === this.fileIds.length;
|
||||
const total = this.fileIds.length + this.folderIds.length;
|
||||
return total > 0
|
||||
&& this.selectedFileIds.length === this.fileIds.length
|
||||
&& this.selectedFolderIds.length === this.folderIds.length;
|
||||
},
|
||||
|
||||
toggleAll(event) {
|
||||
this.selected = event.target.checked ? [...this.fileIds] : [];
|
||||
if (event.target.checked) {
|
||||
this.selectedFileIds = [...this.fileIds];
|
||||
this.selectedFolderIds = [...this.folderIds];
|
||||
} else {
|
||||
this.selectedFileIds = [];
|
||||
this.selectedFolderIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
clearSelection() {
|
||||
this.selected = [];
|
||||
this.selectedFileIds = [];
|
||||
this.selectedFolderIds = [];
|
||||
},
|
||||
|
||||
selectedFiles() {
|
||||
return this.files.filter((file) => this.selected.includes(file.id));
|
||||
selectedFileRows() {
|
||||
return this.files.filter((file) => this.selectedFileIds.includes(file.id));
|
||||
},
|
||||
|
||||
selectedFolderRows() {
|
||||
return this.folders.filter((folder) => this.selectedFolderIds.includes(folder.id));
|
||||
},
|
||||
|
||||
showToast(message) {
|
||||
@@ -554,7 +575,7 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
csrf.value = this.csrf;
|
||||
form.appendChild(csrf);
|
||||
|
||||
this.selected.forEach((id) => {
|
||||
this.selectedFileIds.forEach((id) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'files[]';
|
||||
@@ -562,6 +583,14 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
this.selectedFolderIds.forEach((id) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'folders[]';
|
||||
input.value = id;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
Object.entries(extra).forEach(([key, value]) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
@@ -575,27 +604,41 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
},
|
||||
|
||||
downloadSelected() {
|
||||
if (this.selected.length === 0) return;
|
||||
if (this.selected.length === 1) {
|
||||
const file = this.selectedFiles()[0];
|
||||
if (this.selectedCount === 0) return;
|
||||
|
||||
if (this.selectedFileIds.length === 1 && this.selectedFolderIds.length === 0) {
|
||||
const file = this.selectedFileRows()[0];
|
||||
if (file?.downloadUrl) {
|
||||
window.location.href = file.downloadUrl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedFolderIds.length === 1 && this.selectedFileIds.length === 0) {
|
||||
const folder = this.selectedFolderRows()[0];
|
||||
if (folder?.downloadUrl) {
|
||||
window.location.href = folder.downloadUrl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitAction(this.routes.bulkDownload);
|
||||
},
|
||||
|
||||
async deleteSelected() {
|
||||
if (this.selected.length === 0) return;
|
||||
if (! confirm(`Delete ${this.selected.length} file${this.selected.length === 1 ? '' : 's'}? This cannot be undone.`)) {
|
||||
if (this.selectedFileIds.length === 0) return;
|
||||
if (this.selectedFolderIds.length > 0) {
|
||||
this.showToast('Delete files individually. Folder delete is not supported yet.');
|
||||
return;
|
||||
}
|
||||
if (! confirm(`Delete ${this.selectedFileIds.length} file${this.selectedFileIds.length === 1 ? '' : 's'}? This cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
this.submitAction(this.routes.bulkDelete);
|
||||
},
|
||||
|
||||
async shareSelected() {
|
||||
if (this.selected.length === 0) return;
|
||||
if (this.selectedCount === 0) return;
|
||||
try {
|
||||
const res = await fetch(this.routes.share, {
|
||||
method: 'POST',
|
||||
@@ -605,12 +648,17 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
'X-CSRF-TOKEN': this.csrf,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: JSON.stringify({ files: this.selected }),
|
||||
body: JSON.stringify({
|
||||
files: this.selectedFileIds,
|
||||
folders: this.selectedFolderIds,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.links?.length) {
|
||||
await navigator.clipboard.writeText(data.links.join('\n'));
|
||||
this.showToast(data.message || 'Share links copied.');
|
||||
} else {
|
||||
this.showToast(data.message || 'No share links available.');
|
||||
}
|
||||
} catch (e) {
|
||||
this.showToast('Could not copy share links.');
|
||||
@@ -618,23 +666,31 @@ Alpine.data('filesManager', (config = {}) => ({
|
||||
},
|
||||
|
||||
async copyLinks() {
|
||||
const links = [...new Set(this.selectedFiles().map((f) => f.shareUrl).filter(Boolean))];
|
||||
if (links.length === 0) {
|
||||
const links = [
|
||||
...this.selectedFileRows().map((f) => f.shareUrl),
|
||||
...this.selectedFolderRows().map((f) => f.shareUrl),
|
||||
].filter(Boolean);
|
||||
const uniqueLinks = [...new Set(links)];
|
||||
if (uniqueLinks.length === 0) {
|
||||
this.showToast('No share links available.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(links.join('\n'));
|
||||
this.showToast(links.length === 1 ? 'Link copied.' : `${links.length} links copied.`);
|
||||
await navigator.clipboard.writeText(uniqueLinks.join('\n'));
|
||||
this.showToast(uniqueLinks.length === 1 ? 'Link copied.' : `${uniqueLinks.length} links copied.`);
|
||||
} catch (e) {
|
||||
this.showToast('Could not copy links.');
|
||||
}
|
||||
},
|
||||
|
||||
openInEmail() {
|
||||
if (this.selected.length === 0) return;
|
||||
if (this.selectedFileIds.length === 0) return;
|
||||
if (this.selectedFolderIds.length > 0) {
|
||||
this.showToast('Open in email works with files only.');
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
this.selected.forEach((id) => params.append('files[]', id));
|
||||
this.selectedFileIds.forEach((id) => params.append('files[]', id));
|
||||
window.location.href = `${this.routes.openInEmail}?${params.toString()}`;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user