Fileshare: Mehrfachauswahl zum Herunterladen (ZIP) und Loeschen (v1.1.4)
- Checkbox-Spalte in der Dateitabelle (inkl. "alle auswaehlen" im Tabellen- kopf) -- sobald mindestens ein Eintrag markiert ist, erscheint eine Aktionsleiste mit "Herunterladen (ZIP)" und (mit fileshare.edit) "Loeschen". - Herunterladen buendelt die Auswahl serverseitig zu EINEM ZIP statt mehrerer einzelner Downloads (vermeidet Browser-Blockaden bei vielen gleichzeitigen Downloads) -- neue Route /fileshare/download-multi, Ordner werden dabei rekursiv mit aufgenommen (relativer Pfad als Archivpfad). - Loeschen mehrerer Elemente auf einmal ueber eine neue Route /fileshare/delete-multi (ein Bestaetigungsdialog fuer die ganze Auswahl), mit Sammel-Erfolgsmeldung analog zum Mehrfach-Upload. - Die Pfadaufloesung+Validierung fuer ein einzelnes Kind-Element wurde aus dem bestehenden Einzel-Loeschen in _fileshare_resolve_child() extrahiert und von Einzel-Loeschen, Mehrfach-Loeschen UND Mehrfach-Download gemeinsam genutzt (kein duplizierter Sicherheitscode). Live auf POETEST verifiziert: Mehrfachauswahl inkl. "alle auswaehlen", ZIP-Download zweier Dateien (Inhalt geprueft), ZIP-Download eines Ordners (rekursiv, korrekter Archivpfad), Mehrfach-Loeschen mit Sammelmeldung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,10 +66,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bulkActionsBar" class="flex gap-2 hidden" data-share="{{ selected_share }}" data-path="{{ rel_path }}" style="align-items:center; margin-bottom:10px; flex-wrap:wrap;">
|
||||
<span id="bulkSelectedCount" class="text-faint" style="font-size:12.5px; font-weight:600;"></span>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="bulkDownload()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
|
||||
Herunterladen (ZIP)
|
||||
</button>
|
||||
{% if can_edit %}
|
||||
<button type="button" class="btn btn-sm" style="color:var(--danger); background:transparent; border-color:var(--danger-dim);" onclick="bulkDelete()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0l-1 14a2 2 0 01-2 2H7a2 2 0 01-2-2L4 6"/></svg>
|
||||
Löschen
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if entries %}
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="data-table" id="fileTable" data-sortable>
|
||||
<thead><tr>
|
||||
<th style="width:1%;"><input type="checkbox" id="selectAllFiles"></th>
|
||||
<th data-sort-key="name">Name</th>
|
||||
<th data-sort-key="size">Größe</th>
|
||||
<th data-sort-key="mtime">Geändert</th>
|
||||
@@ -78,6 +93,7 @@
|
||||
<tbody>
|
||||
{% for e in entries %}
|
||||
<tr data-sort-name="{{ e.name|lower }}" data-sort-size="{{ 0 if e.is_dir else e.size_str }}" data-sort-mtime="{{ e.mtime_str }}">
|
||||
<td><input type="checkbox" class="row-select" value="{{ e.name }}"></td>
|
||||
<td class="cell-name">
|
||||
{% if e.is_dir %}
|
||||
<a href="{{ url_for('fileshare', share=selected_share, path=(rel_path ~ '/' ~ e.name) if rel_path else e.name) }}">
|
||||
@@ -228,6 +244,8 @@
|
||||
<script>
|
||||
const FILESHARE_SUBFOLDERS_URL = "{{ url_for('fileshare_subfolders') }}";
|
||||
const FILESHARE_BASE_URL = "{{ url_for('fileshare') }}";
|
||||
const FILESHARE_DOWNLOAD_MULTI_URL = "{{ url_for('fileshare_download_multi') }}";
|
||||
const FILESHARE_DELETE_MULTI_URL = "{{ url_for('fileshare_delete_multi') }}";
|
||||
|
||||
function openRenameModal(name) {
|
||||
document.getElementById("renameOldName").value = name;
|
||||
@@ -243,6 +261,88 @@ function filterTable(inputId, tableId) {
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------------- Mehrfachauswahl (Herunterladen als ZIP / Löschen) ---------------- */
|
||||
|
||||
function getSelectedFileNames() {
|
||||
return Array.from(document.querySelectorAll("#fileTable .row-select:checked")).map(function (cb) { return cb.value; });
|
||||
}
|
||||
|
||||
function updateBulkBar() {
|
||||
const names = getSelectedFileNames();
|
||||
const bar = document.getElementById("bulkActionsBar");
|
||||
const countEl = document.getElementById("bulkSelectedCount");
|
||||
if (!bar) return;
|
||||
if (names.length > 0) {
|
||||
bar.classList.remove("hidden");
|
||||
countEl.textContent = names.length + " ausgewählt";
|
||||
} else {
|
||||
bar.classList.add("hidden");
|
||||
}
|
||||
const selectAll = document.getElementById("selectAllFiles");
|
||||
const allBoxes = document.querySelectorAll("#fileTable .row-select");
|
||||
if (selectAll && allBoxes.length) {
|
||||
selectAll.checked = names.length === allBoxes.length;
|
||||
selectAll.indeterminate = names.length > 0 && names.length < allBoxes.length;
|
||||
}
|
||||
}
|
||||
|
||||
function submitBulkForm(actionUrl, names) {
|
||||
// data-share/data-path statt Jinja-Werte direkt in einen JS-String-
|
||||
// Literal zu setzen -- Ordnernamen kommen von der echten Freigabe und
|
||||
// koennten Anfuehrungszeichen o.ae. enthalten, ueber HTML-Attribute
|
||||
// (von Jinja automatisch escaped) ist das unproblematisch.
|
||||
const bar = document.getElementById("bulkActionsBar");
|
||||
const form = document.createElement("form");
|
||||
form.method = "post";
|
||||
form.action = actionUrl;
|
||||
form.style.display = "none";
|
||||
[["share", bar.dataset.share], ["path", bar.dataset.path]].forEach(function (pair) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = pair[0];
|
||||
input.value = pair[1];
|
||||
form.appendChild(input);
|
||||
});
|
||||
names.forEach(function (name) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = "names";
|
||||
input.value = name;
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function bulkDownload() {
|
||||
const names = getSelectedFileNames();
|
||||
if (!names.length) return;
|
||||
submitBulkForm(FILESHARE_DOWNLOAD_MULTI_URL, names);
|
||||
}
|
||||
|
||||
function bulkDelete() {
|
||||
const names = getSelectedFileNames();
|
||||
if (!names.length) return;
|
||||
window.confirmAction(
|
||||
names.length + " ausgewählte Elemente wirklich endgültig löschen?",
|
||||
function () { submitBulkForm(FILESHARE_DELETE_MULTI_URL, names); },
|
||||
"Auswahl löschen?"
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const selectAll = document.getElementById("selectAllFiles");
|
||||
if (selectAll) {
|
||||
selectAll.addEventListener("change", function () {
|
||||
document.querySelectorAll("#fileTable .row-select").forEach(function (cb) { cb.checked = selectAll.checked; });
|
||||
updateBulkBar();
|
||||
});
|
||||
}
|
||||
document.querySelectorAll("#fileTable .row-select").forEach(function (cb) {
|
||||
cb.addEventListener("change", updateBulkBar);
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------------- Mehrfach-Upload (Multiauswahl + mehrmals nacheinander) ---------------- */
|
||||
/* Ein <input type=file multiple> ERSETZT bei jeder erneuten Dateiauswahl
|
||||
die vorherige -- fuer "mehrmals nacheinander hinzufuegen" wird deshalb
|
||||
|
||||
Reference in New Issue
Block a user