Bilder-Download: Mehrfach-Auswahl + Ordner-ZIP
- Selection-Mode: Bilder in Galerie per Klick auswaehlen - 1-3 Bilder: einzeln herunterladen, 4+: als ZIP - Ordner-Download: ganzen zugewiesenen Ordner als ZIP - Sicherheit: Zugriffspruefung + Path-Traversal-Schutz - archiver Package fuer Streaming-ZIP-Erstellung Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2
assets/css/custom.min.css
vendored
2
assets/css/custom.min.css
vendored
@@ -1 +1 @@
|
||||
.text-fg{color:#a0a0a1}#footer .presentify{margin:0 0 1.4em 0;display:grid;grid-template-columns:auto 1fr;column-gap:1rem;align-items:center}#footer .presentify .presentify-logo{display:flex;align-items:center;justify-content:center}#footer .presentify .presentify-logo a{display:flex;align-items:center;justify-content:center;border:0}#footer .presentify .presentify-logo img{width:72px;height:72px;display:block}#footer .presentify .presentify-text{margin:0}
|
||||
.text-fg{color:#a0a0a1}#selection-toolbar{position:fixed;bottom:4em;left:0;width:100%;background:rgba(31,34,36,.95);padding:.75em 1.5em;display:flex;align-items:center;gap:1em;z-index:10003;transform:translateY(100%);transition:transform .3s ease;box-sizing:border-box}#selection-toolbar.visible{transform:translateY(0)}#selection-toolbar #sel-count{color:#fff;font-weight:400;margin-right:auto;white-space:nowrap}#selection-toolbar button{white-space:nowrap}body.selection-mode #main .thumb{cursor:pointer;position:relative}body.selection-mode #main .thumb::before{content:"";position:absolute;top:.6em;left:.6em;width:1.4em;height:1.4em;border:2px solid rgba(255,255,255,.7);border-radius:3px;background:rgba(0,0,0,.4);z-index:2;transition:all .2s ease;pointer-events:none}body.selection-mode #main .thumb.selected::before{background:#34a58e;border-color:#34a58e}body.selection-mode #main .thumb.selected .image{outline:3px solid #34a58e;outline-offset:-3px}@media screen and (max-width: 736px){#selection-toolbar{bottom:auto;top:4em;transform:translateY(-200%)}#selection-toolbar.visible{transform:translateY(0)}}#footer .presentify{margin:0 0 1.4em 0;display:grid;grid-template-columns:auto 1fr;column-gap:1rem;align-items:center}#footer .presentify .presentify-logo{display:flex;align-items:center;justify-content:center}#footer .presentify .presentify-logo a{display:flex;align-items:center;justify-content:center;border:0}#footer .presentify .presentify-logo img{width:72px;height:72px;display:block}#footer .presentify .presentify-text{margin:0}
|
||||
2
assets/css/light.min.css
vendored
2
assets/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
2
assets/css/main.min.css
vendored
2
assets/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -194,6 +194,7 @@
|
||||
|
||||
initThumbs();
|
||||
initPoptrox();
|
||||
initSelectionMode(images);
|
||||
},
|
||||
error: function() {
|
||||
$main.html('<p style="text-align:center;color:#fff;padding:2em;">Keine Bilder gefunden. Lade Bilder in deinen Nextcloud-Ordner hoch.</p>');
|
||||
@@ -290,6 +291,177 @@
|
||||
return template;
|
||||
}
|
||||
|
||||
// ==================== SELECTION MODE ====================
|
||||
|
||||
function initSelectionMode(allImages) {
|
||||
var selectedImages = [];
|
||||
var selectionActive = false;
|
||||
|
||||
// Toolbar erstellen (versteckt)
|
||||
var $toolbar = $('<div id="selection-toolbar"></div>');
|
||||
$toolbar.html(
|
||||
'<span id="sel-count">0 ausgewaehlt</span>' +
|
||||
'<button id="sel-download" class="button small" disabled>' +
|
||||
'<i class="fa fa-download"></i> Herunterladen</button>' +
|
||||
'<button id="sel-cancel" class="button small">' +
|
||||
'<i class="fa fa-times"></i> Abbrechen</button>'
|
||||
);
|
||||
$('body').append($toolbar);
|
||||
|
||||
// Selection-Toggle Button im Header
|
||||
var $selectToggle = $('<li><a href="#" id="select-mode-toggle" title="Bilder auswaehlen">' +
|
||||
'<i class="fa fa-check-square"></i></a></li>');
|
||||
$('#header nav > ul').prepend($selectToggle);
|
||||
|
||||
// Ordner-Download Buttons (nur wenn Ordner-Bilder vorhanden)
|
||||
var folders = [];
|
||||
allImages.forEach(function(img) {
|
||||
if (img.folder && folders.indexOf(img.folder) === -1) {
|
||||
folders.push(img.folder);
|
||||
}
|
||||
});
|
||||
if (folders.length > 0) {
|
||||
folders.forEach(function(folder) {
|
||||
var $folderBtn = $('<li><a href="#" class="folder-download-btn" ' +
|
||||
'data-folder="' + folder + '" title="Ordner \'' + folder + '\' herunterladen">' +
|
||||
'<i class="fa fa-folder-open"></i></a></li>');
|
||||
$('#header nav > ul').prepend($folderBtn);
|
||||
});
|
||||
}
|
||||
|
||||
// Selection-Mode toggle
|
||||
$(document).on('click', '#select-mode-toggle', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
selectionActive = !selectionActive;
|
||||
$('body').toggleClass('selection-mode', selectionActive);
|
||||
if (!selectionActive) clearSelection();
|
||||
$toolbar.toggleClass('visible', selectionActive);
|
||||
});
|
||||
|
||||
// Klick auf Thumbnail im Selection-Mode
|
||||
$(document).on('click', '.thumb', function(e) {
|
||||
if (!selectionActive) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var $thumb = $(this);
|
||||
var $img = $thumb.find('img');
|
||||
var imgName = $img.data('name');
|
||||
var imgHref = $thumb.find('a.image').attr('href');
|
||||
|
||||
// Ordner aus href extrahieren
|
||||
var folder = null;
|
||||
var hrefPath = imgHref.replace('/images/fulls/', '');
|
||||
var parts = hrefPath.split('/');
|
||||
if (parts.length > 1) {
|
||||
folder = decodeURIComponent(parts[0]);
|
||||
}
|
||||
|
||||
var idx = -1;
|
||||
for (var i = 0; i < selectedImages.length; i++) {
|
||||
if (selectedImages[i].name === imgName && selectedImages[i].folder === folder) {
|
||||
idx = i; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx !== -1) {
|
||||
selectedImages.splice(idx, 1);
|
||||
$thumb.removeClass('selected');
|
||||
} else {
|
||||
selectedImages.push({ name: imgName, folder: folder });
|
||||
$thumb.addClass('selected');
|
||||
}
|
||||
updateToolbar();
|
||||
});
|
||||
|
||||
function updateToolbar() {
|
||||
var count = selectedImages.length;
|
||||
$('#sel-count').text(count + ' ausgewaehlt');
|
||||
$('#sel-download').prop('disabled', count === 0);
|
||||
if (count > 3) {
|
||||
$('#sel-download').html('<i class="fa fa-file-archive-o"></i> Als ZIP (' + count + ')');
|
||||
} else if (count > 0) {
|
||||
$('#sel-download').html('<i class="fa fa-download"></i> Herunterladen (' + count + ')');
|
||||
} else {
|
||||
$('#sel-download').html('<i class="fa fa-download"></i> Herunterladen');
|
||||
}
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedImages = [];
|
||||
$('.thumb').removeClass('selected');
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
// Download-Button
|
||||
$(document).on('click', '#sel-download', function() {
|
||||
if (selectedImages.length === 0) return;
|
||||
$.ajax({
|
||||
url: '/api/download/selected',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ images: selectedImages }),
|
||||
xhrFields: { responseType: 'blob' },
|
||||
success: function(data, status, xhr) {
|
||||
var ct = xhr.getResponseHeader('Content-Type');
|
||||
if (ct && ct.indexOf('application/json') !== -1) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
var resp = JSON.parse(this.result);
|
||||
if (resp.mode === 'individual') {
|
||||
resp.urls.forEach(function(url, i) {
|
||||
setTimeout(function() {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}, i * 300);
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(data);
|
||||
} else {
|
||||
var url = window.URL.createObjectURL(data);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'mischkomposition-auswahl.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
alert(xhr.responseJSON ? xhr.responseJSON.error : 'Download fehlgeschlagen');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Abbrechen-Button
|
||||
$(document).on('click', '#sel-cancel', function() {
|
||||
selectionActive = false;
|
||||
$('body').removeClass('selection-mode');
|
||||
$toolbar.removeClass('visible');
|
||||
clearSelection();
|
||||
});
|
||||
|
||||
// Ordner-Download
|
||||
$(document).on('click', '.folder-download-btn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var folder = $(this).data('folder');
|
||||
var a = document.createElement('a');
|
||||
a.href = '/api/download/folder/' + encodeURIComponent(folder);
|
||||
a.download = folder + '.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
}
|
||||
|
||||
// Galerie laden
|
||||
loadGallery();
|
||||
|
||||
|
||||
2
assets/js/main.min.js
vendored
2
assets/js/main.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -6,6 +6,85 @@
|
||||
color: _palette(fg);
|
||||
}
|
||||
|
||||
/* ==================== Selection Mode ==================== */
|
||||
|
||||
#selection-toolbar {
|
||||
position: fixed;
|
||||
bottom: _size(header);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: rgba(31, 34, 36, 0.95);
|
||||
padding: 0.75em 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
z-index: _misc(z-index-base) + 3;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
#sel-count {
|
||||
color: _palette(fg-bold);
|
||||
font-weight: _font(weight-extrabold);
|
||||
margin-right: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
body.selection-mode {
|
||||
#main .thumb {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0.6em;
|
||||
left: 0.6em;
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
border: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-radius: 3px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 2;
|
||||
transition: all 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
&::before {
|
||||
background: _palette(accent1);
|
||||
border-color: _palette(accent1);
|
||||
}
|
||||
|
||||
.image {
|
||||
outline: 3px solid _palette(accent1);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 736px) {
|
||||
#selection-toolbar {
|
||||
bottom: auto;
|
||||
top: _size(header);
|
||||
transform: translateY(-200%);
|
||||
|
||||
&.visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
#footer {
|
||||
.presentify {
|
||||
|
||||
Reference in New Issue
Block a user