Galerie Block mit PHP
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
ÄH - habe doch eben eine Kategorie mit Bildern angelegt - nu isse weg 
Re: Galerie Block mit PHP
Nochmal geändert, war ein Bug drin Tommy
Jetzt aber 100% OK
vn-gallery heisst die
Kannst Ibiza wieder rein machen
Die soll ja auch ohne Bilder starten können - tut sie jetzt
Jetzt aber 100% OK
vn-gallery heisst die
Kannst Ibiza wieder rein machen
Die soll ja auch ohne Bilder starten können - tut sie jetzt
Re: Galerie Block mit PHP
Gar nicht. Löschen neu machen - kannst ja alle bilder auch später noch zuordnen.Tommy Herrmann hat geschrieben: Di 7. Jul 2026, 09:00 Tolle Galerie - werde ich auch mal mit aufnehmen
Wie ändere ich denn den Kategorie-Namen?
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
Wann ist die den halbwegs fertig und im Download?
Frage nur, ob ich besser noch warten soll. Werden die Dateinamen wieder in JSON gespeichert?
Frage nur, ob ich besser noch warten soll. Werden die Dateinamen wieder in JSON gespeichert?
Re: Galerie Block mit PHP
Ist jetzt fertig Tommy
Die Bilder werden im uploads Ordner gespeichert.
Alles andere in einer json wie immer
Ist jetzt im Download verfügbar und wird jetzt auch nicht mehr geädnert, auch wenn Herr Stober noch was findet
Die Bilder werden im uploads Ordner gespeichert.
Alles andere in einer json wie immer
Ist jetzt im Download verfügbar und wird jetzt auch nicht mehr geädnert, auch wenn Herr Stober noch was findet
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
tut mir leid - ich finde diese Galerie nicht im Download 
https://www.niederastroth.de/vngalerie/
Wie heißt die denn dort?
P.S.:
… oder ist sie das? Das ist wirklich reines Raten
https://www.niederastroth.de/vngalerie/
Wie heißt die denn dort?
P.S.:
… oder ist sie das? Das ist wirklich reines Raten
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
OK - bin dabei ...
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
Ja - super tolle Galerie - ähnlich der Anwendung von Mobirise mit den Kategorien gelöst, nur das man hier beliebig viele Fotos hochladen kann - viel schneller und bequemer als mit Mobirise und eben als CMS online.
> Ich habe (leider) keine Fehler finden können
> Wie ändert man das Administrator-Passwort ohne alles neu machen zu müssen, geht das?
> Was mir fehlt ist die Option beim Starten eine Kategorie zu wählen, also diese als Favoriten zu kennzeichnen. Wenn man extrem viele Fotos hat, möchte man vielleicht nicht gleich "Alle" Fotos laden aber dennoch das laden der Fotos nicht ganz abstellen.
Ich mache jetzt mal eine Pause mit dem Upload, falls da alles nochmals gemacht werden muss.
Hier ist meine Testseite:
https://www.mobirise-tutorials.com/VN-B ... pload.html
> Ich habe (leider) keine Fehler finden können
> Wie ändert man das Administrator-Passwort ohne alles neu machen zu müssen, geht das?
> Was mir fehlt ist die Option beim Starten eine Kategorie zu wählen, also diese als Favoriten zu kennzeichnen. Wenn man extrem viele Fotos hat, möchte man vielleicht nicht gleich "Alle" Fotos laden aber dennoch das laden der Fotos nicht ganz abstellen.
Ich mache jetzt mal eine Pause mit dem Upload, falls da alles nochmals gemacht werden muss.
Hier ist meine Testseite:
Re: Galerie Block mit PHP
@Tommy:
Das Passwort zu ändern war nie vorgesewhen, aber da ich ja ne Menge zeit habe - Gibts jetzt einen Master Passwort änderer für alle Blöcke die eins haben. Da schöne daran, alles kann online bleiben nur das Passwort wird neu inizialisiert also muss man dann wieder 2 x eingeben bei Aufruf des Admin.
Schau mal: https://www.niederastroth.de/vngalerie/reset-pw.php
der Schlüssel ist: reset-2024
Bei der Galerie, wenn man schon mehere angelegt hat, muss das bei jeder gemacht werden, weil die ja auch unterschiedliche Passwörter haben können. Ein Service Tool von V.Niederastroth
Alles andere kann und will ich jetzt nicht mehr ändern. Wenn Du eine andere Start Galerie haben möchtest, weisst du ja auch wie man wo was ändern muss
Hier das PHP Script:
Das Passwort zu ändern war nie vorgesewhen, aber da ich ja ne Menge zeit habe - Gibts jetzt einen Master Passwort änderer für alle Blöcke die eins haben. Da schöne daran, alles kann online bleiben nur das Passwort wird neu inizialisiert also muss man dann wieder 2 x eingeben bei Aufruf des Admin.
Schau mal: https://www.niederastroth.de/vngalerie/reset-pw.php
der Schlüssel ist: reset-2024
Bei der Galerie, wenn man schon mehere angelegt hat, muss das bei jeder gemacht werden, weil die ja auch unterschiedliche Passwörter haben können. Ein Service Tool von V.Niederastroth
Alles andere kann und will ich jetzt nicht mehr ändern. Wenn Du eine andere Start Galerie haben möchtest, weisst du ja auch wie man wo was ändern muss
Hier das PHP Script:
Code: Alles auswählen
<?php
// ============================================================
// reset-pw.php – VN Block Passwort-Reset
// Per FTP hochladen, aufrufen, danach SOFORT wieder löschen!
// ============================================================
$masterKey = 'reset-2024'; // ← Diesen Key ändern!
// Feste Blocks
$blocks = [
'VN Feedback' => 'assets/vn-feedback/config/pw.hash',
'VN Chat' => 'assets/vn-chat/config/pw.hash',
'VN Subscribe' => 'assets/vn-subscribe/config/pw.hash',
'VN Download' => 'assets/vn-download/config/pw.hash',
'VN HeaderAnim' => 'assets/vn-headeranim/config/pw.hash',
];
// VN Gallery: dynamisch alle Galerie-IDs scannen
$galleryConfig = __DIR__ . '/assets/vn-gallery/config';
if (is_dir($galleryConfig)) {
foreach (scandir($galleryConfig) as $entry) {
if ($entry === '.' || $entry === '..') continue;
$pwFile = $galleryConfig . '/' . $entry . '/pw.hash';
if (is_dir($galleryConfig . '/' . $entry)) {
$blocks['VN Gallery: ' . $entry] = 'assets/vn-gallery/config/' . $entry . '/pw.hash';
}
}
}
$msg = '';
$action = $_POST['action'] ?? '';
$key = $_POST['key'] ?? '';
$block = $_POST['block'] ?? '';
if ($action === 'reset' && $key === $masterKey && isset($blocks[$block])) {
$file = __DIR__ . '/' . $blocks[$block];
if (is_readable($file)) {
unlink($file);
$msg = ['ok', "✓ Passwort für <b>$block</b> zurückgesetzt. Beim nächsten Aufruf erscheint das Setup-Formular."];
} else {
$msg = ['err', "Datei nicht gefunden: {$blocks[$block]}"];
}
}
if ($action === 'selfdelete' && $key === $masterKey) {
@unlink(__FILE__);
echo '<p style="font-family:Arial;color:green;padding:2rem;">✓ reset-pw.php wurde gelöscht.</p>';
exit;
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>VN Passwort-Reset</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #0a0a0a; color: #fff; display: flex; align-items: center;
justify-content: center; min-height: 100vh; padding: 1rem; }
.box { background: #141414; border: 1px solid #222; border-radius: 16px;
padding: 2rem; width: 100%; max-width: 460px; }
h2 { color: #1d9e75; font-size: 1.1rem; font-weight: 700; margin-bottom: 0.4rem; }
p.sub { color: #555; font-size: 0.8rem; margin-bottom: 1.5rem; }
label { display: block; color: #888; font-size: 0.78rem; text-transform: uppercase;
letter-spacing: 0.06em; margin-bottom: 0.35rem; margin-top: 1rem; }
input, select { background: #0d0d0d; border: 1px solid #2a2a2a; border-radius: 8px;
padding: 0.65rem 1rem; color: #fff; font-size: 0.9rem;
width: 100%; outline: none; }
input:focus, select:focus { border-color: #1d9e75; }
select option { background: #1a1a1a; }
.btn { background: #1d9e75; color: #fff; border: none; border-radius: 8px;
padding: 0.75rem 1.5rem; font-size: 0.92rem; font-weight: 700;
cursor: pointer; width: 100%; margin-top: 1.25rem; }
.btn:hover { filter: brightness(1.1); }
.msg { border-radius: 8px; padding: 0.75rem 1rem; margin-top: 1.25rem;
font-size: 0.88rem; }
.ok { background: rgba(29,158,117,0.1); border: 1px solid rgba(29,158,117,0.3); color: #1d9e75; }
.err { background: rgba(231,76,60,0.1); border: 1px solid rgba(231,76,60,0.3); color: #e74c3c; }
.warn { background: rgba(241,196,15,0.08); border: 1px solid rgba(241,196,15,0.2);
color: #f9e07e; font-size: 0.8rem; padding: 0.65rem 1rem;
border-radius: 8px; margin-top: 1.25rem; }
hr { border: none; border-top: 1px solid #222; margin: 1.5rem 0; }
.del-btn { background: transparent; border: 1px solid #333; color: #555;
border-radius: 8px; padding: 0.55rem 1rem; font-size: 0.82rem;
cursor: pointer; width: 100%; margin-top: 0.5rem; }
.del-btn:hover { border-color: #e74c3c; color: #e74c3c; }
</style>
</head>
<body>
<div class="box">
<h2>🔑 VN Passwort-Reset</h2>
<p class="sub">Setzt das Admin-Passwort eines Blocks zurück.<br>
Beim nächsten Aufruf erscheint das Setup-Formular.</p>
<?php if ($msg): ?>
<div class="msg <?= $msg[0] ?>"><?= $msg[1] ?></div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="action" value="reset"/>
<label>Master-Schlüssel</label>
<input type="password" name="key" placeholder="Master-Schlüssel eingeben"
autocomplete="off" required/>
<label>Block auswählen</label>
<select name="block">
<?php foreach ($blocks as $name => $path): ?>
<option value="<?= htmlspecialchars($name) ?>"
<?= ($block === $name ? 'selected' : '') ?>>
<?= htmlspecialchars($name) ?>
<?= is_readable(__DIR__ . '/' . $path) ? '' : ' (kein Passwort gesetzt)' ?>
</option>
<?php endforeach; ?>
</select>
<button class="btn" type="submit">Passwort zurücksetzen</button>
</form>
<div class="warn">
⚠️ Diese Datei nach der Nutzung unbedingt löschen!
</div>
<hr>
<p style="color:#555;font-size:0.78rem;margin-bottom:0.5rem;">Script sicher löschen:</p>
<form method="post">
<input type="hidden" name="action" value="selfdelete"/>
<input type="password" name="key" placeholder="Master-Schlüssel" autocomplete="off"/>
<button class="del-btn" type="submit">🗑 reset-pw.php jetzt löschen</button>
</form>
</div>
</body>
</html>
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
> Die "VN Gallery" ist nicht in der Online-Galerie zu finden - oder gucke ich falsch 
https://www.niederastroth.de/mobirisebloecke.php
https://www.niederastroth.de/mobirisebloecke.php
Re: Galerie Block mit PHP
Du guckst falsch
Ist doch unter mobirise & PHP drin da wo sie hin gehört
Da sind alle Blöcke egal was für ein Typ, zusammengafsst weil die eben PHP in der mbrext haben und nur mit import funktionieren
Ist doch unter mobirise & PHP drin da wo sie hin gehört
Da sind alle Blöcke egal was für ein Typ, zusammengafsst weil die eben PHP in der mbrext haben und nur mit import funktionieren
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
OK - aber bis eben war sie noch in der importierten VN-Block-Galerie unter "Galerie", so hatte ich auch mein Tutorial geschrieben. Ich habe also nicht falsch geguckt sondern Du hast es gerade geändert 
Ich finde es unlogisch - ich würde eine Galerie eben immer auch unter "Galerie" suchen - wenn es diese Kategorie schon gibt.
Ich finde es unlogisch - ich würde eine Galerie eben immer auch unter "Galerie" suchen - wenn es diese Kategorie schon gibt.
Re: Galerie Block mit PHP
Ja Tommy und dann machen es alle so wie Frank, ziehen den Block rein und wundern sich das es nicht geht.
NEIN!! ich lasse das genau so. Diese Blöcke sind Spezial Anwendungen. Die haben ein PHP BAckend die müssen importiert werden. Die sind auch in der VN Block Galerie, weil eben ein Ordner für alle Erweiterungen.
MAn muss eben unterscheiden - Blöcke die kein PHP haben kann man einfach einfügen mit der VN Block Galerie. Mobirise&PHP NICHT!!! Die müssen zwingend per download (egal ob aus web Galerie oder aus Mobirise) gespeichert werden und in Mobirise mittels Import eingelesen werden!!
DAs ist meine Logik dahinter
PS: Das habe ich hier bereits mehrfach erwähnt und sage das auch in jedem Video. Ich denke nun sollte auch der letzte DAU das verstanden haben. Alles was nicht in der Kategorie Mobirse&PHP steht kann einfach in der VN Block Galerie in Mobirise hinzugefügt werden. Dann ist der Block sofort im Buillder ( als letzter Block)
Sobald ihr PHP lest !!! Achtung !!!! Nur Import möglich
NEIN!! ich lasse das genau so. Diese Blöcke sind Spezial Anwendungen. Die haben ein PHP BAckend die müssen importiert werden. Die sind auch in der VN Block Galerie, weil eben ein Ordner für alle Erweiterungen.
MAn muss eben unterscheiden - Blöcke die kein PHP haben kann man einfach einfügen mit der VN Block Galerie. Mobirise&PHP NICHT!!! Die müssen zwingend per download (egal ob aus web Galerie oder aus Mobirise) gespeichert werden und in Mobirise mittels Import eingelesen werden!!
DAs ist meine Logik dahinter
PS: Das habe ich hier bereits mehrfach erwähnt und sage das auch in jedem Video. Ich denke nun sollte auch der letzte DAU das verstanden haben. Alles was nicht in der Kategorie Mobirse&PHP steht kann einfach in der VN Block Galerie in Mobirise hinzugefügt werden. Dann ist der Block sofort im Buillder ( als letzter Block)
Sobald ihr PHP lest !!! Achtung !!!! Nur Import möglich
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
ChatGPT hat mit das im HTML-Code über die Block-Werkzeuge angepasst, man kann jetzt das Feld:
Start-Kategorie (optional)
... beliebig beschreiben.
https://www.mobirise-tutorials.com/VN-B ... vn-gallery
ChatGPT hat im HTML-Code auch einen Fehler gefunden:
Genau, so ist die Logik jetzt aufgebaut:
> Feld leer → alle Fotos werden gezeigt.
> Kategorie existiert, egal ob groß oder klein geschrieben → diese Kategorie wird gezeigt.
> Kategorie existiert nicht, z. B. Haus → Fallback auf alle Fotos.
> Schalter „Beim Start alle Bilder zeigen“ aus → beim Start keine Fotos, auch wenn im Feld etwas steht.
Hier der neue HTML-Code:
Start-Kategorie (optional)
... beliebig beschreiben.
ChatGPT hat im HTML-Code auch einen Fehler gefunden:
ChatGPT hat geschrieben:
Wichtig: In Deinem vorhandenen Script wird startWithAll zwar ausgelesen, aber beim Start praktisch nicht richtig benutzt. Ich korrigiere das gleich mit, damit „Beim Start alle Bilder zeigen“ weiterhin sinnvoll funktioniert.
Genau, so ist die Logik jetzt aufgebaut:
> Feld leer → alle Fotos werden gezeigt.
> Kategorie existiert, egal ob groß oder klein geschrieben → diese Kategorie wird gezeigt.
> Kategorie existiert nicht, z. B. Haus → Fallback auf alle Fotos.
> Schalter „Beim Start alle Bilder zeigen“ aus → beim Start keine Fotos, auch wenn im Feld etwas steht.
Hier der neue HTML-Code:
Code: Alles auswählen
<section data-bs-version="5.1" class="vn-gallery" group="Content" plugins="vn-gallery" style="
--vg-bg: {{bgColor}};
--vg-card: {{cardBgColor}};
--vg-accent: {{accentColor}};
--vg-text: {{textColor}};
--vg-sub: {{subtextColor}};
--vg-gap: {{gridGap}}px;
--vg-radius: {{cardRadius}}px;
--vg-pt: {{paddingTop}}rem;
--vg-pb: {{paddingBottom}}rem;
" data-endpoint="assets/vn-gallery/gallery.php" data-gid="{{galleryId}}" data-itemsize="{{itemSize}}" data-perpage="{{perPage}}" data-showallbtn="{{showAllBtn}}" data-startwithall="{{startWithAll}}" data-startcategory="{{startCategory}}" data-showborder="{{showBorder}}" data-borderw="{{borderWidth}}" data-borderc="{{borderColor}}" data-lblur="{{lbBlur}}" data-lbeffect="{{lbEffect}}" data-lbbgcolor="{{lbBgColor}}" data-lbbgopacity="{{lbBgOpacity}}">
<mbr-parameters>
<header>Galerie</header>
<input type="text" title="Galerie-ID (eindeutig pro Block)" name="galleryId" value="galerie-1">
<input type="checkbox" title="Alle-Button anzeigen" name="showAllBtn" checked>
<input type="checkbox" title="Beim Start alle Bilder zeigen" name="startWithAll" checked>
<!-- STARTKATEGORIE:
Optionales neues Feld.
Leer = Originalverhalten, also alle Bilder beim Start, sofern der Start-Schalter aktiv ist.
Beispiel: Ibiza = beim Start nur Kategorie Ibiza anzeigen.
-->
<input type="text" title="Start-Kategorie (optional)" name="startCategory" value>
<header>Layout</header>
<input type="checkbox" title="Full Width" name="fullWidth">
<input type="range" inline title="Bildgröße (px)" name="itemSize" min="80" max="400" step="20" value="200">
<input type="range" inline title="Bilder pro Seite" name="perPage" min="4" max="48" step="4" value="12">
<input type="range" inline title="Abstand (px)" name="gridGap" min="4" max="24" step="2" value="16">
<input type="range" inline title="Top" name="paddingTop" min="0" max="12" step="1" value="5">
<input type="range" inline title="Bottom" name="paddingBottom" min="0" max="12" step="1" value="6">
<header>Farben</header>
<input type="color" title="Hintergrund" value="#00276d" name="bgColor" selected>
<input type="color" title="Karte" value="#1a1a1a" name="cardBgColor" selected>
<input type="color" title="Akzent" value="#9fdbf8" name="accentColor" selected>
<input type="color" title="Text" value="#ffffff" name="textColor" selected>
<input type="color" title="Subtext" value="#ffffff" name="subtextColor" selected>
<header>Lightbox</header>
<select title="Einblend-Effekt" name="lbEffect">
<option value="fade" selected>Einblenden</option>
<option value="slide">Schieben</option>
<option value="zoom">Zoom</option>
</select>
<input type="color" title="Hintergrund-Farbe" name="lbBgColor" value="#000000" selected>
<input type="range" inline title="Hintergrund-Deckkraft (%)" name="lbBgOpacity" min="50" max="100" step="5" value="80">
<input type="range" inline title="Hintergrund-Blur (px)" name="lbBlur" min="0" max="20" step="1" value="0">
<input type="checkbox" title="Rahmen um Bilder" name="showBorder" checked>
<input type="color" title="Rahmen-Farbe" name="borderColor" value="#ffffff" condition="showBorder">
<input type="range" inline title="Rahmen-Breite (px)" name="borderWidth" min="1" max="8" step="1" value="2" condition="showBorder">
<header>Design</header>
<input type="range" inline title="Eckenrundung (px)" name="cardRadius" min="0" max="20" step="2" value="20">
</mbr-parameters>
<div mbr-class="{'container':!fullWidth,'container-fluid':fullWidth}">
<h2 class="vg-title mbr-fonts-style display-2" mbr-text data-app-selector=".vg-title" mbr-theme-style="display-2">Galerie</h2>
<div class="vg-root" id="vg-root-{{galleryId}}">
<p class="vg-placeholder">⟳ Galerie wird geladen…</p>
</div>
</div>
<script>
(function(){
var sec = document.currentScript ? document.currentScript.closest('section.vn-gallery') : null;
if (!sec) sec = document.querySelector('section.vn-gallery');
if (!sec) return;
// STARTKATEGORIE MIT MOBIRISE-SCHALTER:
// Die Start-Kategorie wird jetzt aus dem neuen Mobirise-Feld gelesen:
// "Start-Kategorie (optional)".
//
// Feld leer:
// - Mobirise-Schalter "Beim Start alle Bilder zeigen" EIN:
// Es werden alle Bilder angezeigt.
//
// Feld mit Kategorie, z.B. "Ibiza":
// - Mobirise-Schalter "Beim Start alle Bilder zeigen" EIN:
// Es wird beim Start nur diese Kategorie angezeigt.
//
// Mobirise-Schalter "Beim Start alle Bilder zeigen" AUS:
// - Es werden beim Start GAR KEINE Bilder angezeigt,
// egal ob im Feld eine Kategorie steht oder nicht.
//
// Wichtig:
// Die Kategorie muss in der Galerie genauso angelegt sein.
// Groß-/Kleinschreibung ist egal: "Ibiza", "ibiza" oder "IBIZA" funktionieren.
var startCategoryName = (sec.dataset.startcategory || '').trim();
// STARTKATEGORIE MIT MOBIRISE-SCHALTER:
// Diese Variable verhindert, dass die Startkategorie später bei Admin-Aktionen,
// Uploads oder erneutem Laden der Galerie immer wieder zwangsweise zurückgesetzt wird.
var initialCategoryApplied = false;
// Alle dynamischen Lightbox-Styles per JS – kein LESS
var _dynStyle = document.createElement('style');
document.head.appendChild(_dynStyle);
function applyLbStyles() {
try {
var blurPx = parseInt(sec.dataset.lblur || '0', 10);
var showBrd = (sec.dataset.showborder !== 'false' && sec.dataset.showborder !== '' && sec.dataset.showborder !== undefined);
var brdW = (sec.dataset.borderw || '2').replace('px','');
var brdC = sec.dataset.borderc || '#1d9e75';
var css = '';
if (blurPx > 0)
css += '#vg-lightbox-bg{-webkit-backdrop-filter:blur('+blurPx+'px);backdrop-filter:blur('+blurPx+'px)}';
if (showBrd)
css += '.vg-item-inner{box-shadow:inset 0 0 0 '+brdW+'px '+brdC+'}';
_dynStyle.textContent = css;
} catch(e) {}
}
applyLbStyles();
var ep = (sec.dataset.endpoint || 'assets/vn-gallery/gallery.php').trim();
var gid = (sec.dataset.gid || 'galerie-1').trim();
var itemSize = parseInt(sec.dataset.itemsize || '200', 10);
var perPage = parseInt(sec.dataset.perpage || '12', 10);
var showAllBtn = (sec.dataset.showallbtn === 'true' || sec.dataset.showallbtn === '1');
var startWithAll = (sec.dataset.startwithall === 'true' || sec.dataset.startwithall === '1');
var showBorder = (sec.dataset.showborder === 'true' || sec.dataset.showborder === '1');
var rootId = 'vg-root-' + gid;
var root = document.getElementById(rootId);
if (!root) return;
// Editor-Vorschau
if (window.location.protocol === 'file:' || window.location.port === '409') {
root.innerHTML = '<div class="vg-editor-hint">'
+ '<span style="font-size:1.5rem;">📷</span>'
+ '<div><strong style="color:#fff;display:block;margin-bottom:0.25rem;">VN Gallery · ' + gid + '</strong>'
+ '<span style="color:#888;font-size:0.82rem;">4x Tippen = Admin (Handy) · Strg+Doppelklick = Admin (Desktop)</span></div>'
+ '</div>';
return;
}
var images = [];
var categories = [];
var isAdmin = false;
var firstRun = false;
var curPage = 0;
// STARTKATEGORIE MIT MOBIRISE-SCHALTER:
// null = beim Start keine Bilder anzeigen
// '' = alle Bilder anzeigen
// ID = bestimmte Kategorie anzeigen
//
// Wenn der Mobirise-Schalter AUS ist, startet activeCat mit null.
// Dann wird beim Start nichts angezeigt.
//
// Wenn der Mobirise-Schalter EIN ist, startet activeCat zunächst mit ''.
// Nach dem Laden der Kategorien wird geprüft, ob im neuen Feld eine Start-Kategorie steht.
var activeCat = startWithAll ? '' : null;
function ep$(a) { return ep + '?action=' + a + '&gid=' + encodeURIComponent(gid); }
function post(a, data, cb) {
var fd = new FormData();
fd.append('action', a);
fd.append('gid', gid);
if (data) Object.keys(data).forEach(function(k){ fd.append(k, data[k]); });
fetch(ep, {method:'POST', body:fd}).then(function(r){return r.json();}).then(cb).catch(function(){});
}
function get(a, cb) {
fetch(ep$(a)).then(function(r){return r.json();}).then(cb).catch(function(){});
}
// STARTKATEGORIE MIT MOBIRISE-SCHALTER:
// Diese Funktion wird erst nach dem Laden der Kategorien ausgeführt.
// Nur dann ist die interne Kategorie-ID bekannt.
function applyInitialCategory() {
if (initialCategoryApplied) return;
initialCategoryApplied = true;
// Wenn der Mobirise-Schalter "Beim Start alle Bilder zeigen" AUS ist,
// soll beim Start bewusst gar nichts angezeigt werden.
// In diesem Fall wird auch keine Start-Kategorie automatisch aktiviert.
if (!startWithAll) {
activeCat = null;
curPage = 0;
return;
}
// Wenn das neue Feld "Start-Kategorie" leer ist,
// bleibt das Originalverhalten erhalten:
// Der Schalter ist EIN, also werden alle Bilder angezeigt.
var wanted = (startCategoryName || '').trim().toLowerCase();
if (!wanted) {
activeCat = '';
curPage = 0;
return;
}
// Wenn im neuen Feld eine Kategorie steht,
// wird diese Kategorie gesucht und beim Start aktiviert.
var found = categories.find(function(c) {
return (c.name || '').trim().toLowerCase() === wanted;
});
if (found) {
activeCat = found.id;
curPage = 0;
return;
}
// Fallback:
// Falls die eingetragene Kategorie nicht gefunden wird,
// bleibt das bisherige Verhalten erhalten:
// Der Schalter ist EIN, also werden alle Bilder angezeigt.
activeCat = '';
curPage = 0;
}
// ── DOM aufbauen ─────────────────────────────────────────
root.innerHTML = '';
// Admin-Toolbar
var toolbar = document.createElement('div');
toolbar.className = 'vg-toolbar';
toolbar.innerHTML =
'<div class="vg-admin-bar" id="vg-admin-bar-'+gid+'" style="display:none">'
+ '<span class="vg-badge">⚙️ Admin · ' + gid + '</span>'
+ '<button class="vg-btn" id="vg-up-toggle-'+gid+'">↑ Hochladen</button>'
+ '<button class="vg-btn" id="vg-cat-toggle-'+gid+'" style="background:rgba(29,158,117,0.2);color:#1d9e75;border:1px solid rgba(29,158,117,0.3);">📁 Kategorien</button>'
+ '<button class="vg-btn-ghost" id="vg-logout-btn-'+gid+'">Abmelden</button>'
+ '</div>';
root.appendChild(toolbar);
// Upload-Zone
var upZone = document.createElement('div');
upZone.className = 'vg-upload-zone';
upZone.style.display = 'none';
upZone.id = 'vg-upzone-' + gid;
upZone.innerHTML =
'<label class="vg-drop-area" for="vg-file-input-'+gid+'">'
+ '<span style="font-size:2rem;display:block;margin-bottom:0.5rem;">📁</span>'
+ '<p style="margin:0 0 0.25rem;color:#fff;font-size:0.9rem;" id="vg-drop-txt-'+gid+'">Bilder hier reinziehen oder klicken</p>'
+ '<p style="color:#fff;font-size:0.75rem;margin:0;">JPG · PNG · WebP · max. 10 MB · Mehrfachauswahl möglich</p>'
+ '<input type="file" id="vg-file-input-'+gid+'" multiple accept="image/*" style="display:none"/>'
+ '</label>'
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;margin-top:0.5rem;">'
+ '<input type="text" id="vg-cap-input-'+gid+'" class="vg-input" placeholder="Beschriftung (optional)"/>'
+ '<select id="vg-cat-select-'+gid+'" class="vg-input vg-select"><option value>– Keine Kategorie –</option></select>'
+ '</div>'
+ '<div id="vg-prog-wrap-'+gid+'" style="display:none;margin-top:0.75rem;">'
+ '<div id="vg-prog-bar-'+gid+'" style="height:3px;background:#1d9e75;width:0%;border-radius:2px;transition:width 0.2s;"></div>'
+ '<p id="vg-prog-txt-'+gid+'" style="color:#888;font-size:0.78rem;margin:0.4rem 0 0;text-align:center;"></p>'
+ '</div>'
+ '<button class="vg-btn" id="vg-upload-btn-'+gid+'" style="margin-top:0.75rem;">Hochladen</button>';
root.appendChild(upZone);
// Kategorien-Panel
var catPanel = document.createElement('div');
catPanel.className = 'vg-cat-panel';
catPanel.style.display = 'none';
catPanel.id = 'vg-catpanel-' + gid;
catPanel.innerHTML =
'<h4 class="vg-panel-title">📁 Kategorien verwalten</h4>'
+ '<div id="vg-cat-list-'+gid+'" class="vg-cat-list-admin"></div>'
+ '<div style="display:flex;gap:0.5rem;margin-top:0.5rem;">'
+ '<input type="text" id="vg-new-cat-'+gid+'" class="vg-input" placeholder="Neue Kategorie…" style="flex:1;"/>'
+ '<button class="vg-btn" id="vg-add-cat-'+gid+'">Anlegen</button>'
+ '</div>';
root.appendChild(catPanel);
// Filter-Buttons
var filterBar = document.createElement('div');
filterBar.className = 'vg-filter-bar';
filterBar.id = 'vg-filter-' + gid;
filterBar.style.display = 'none';
root.appendChild(filterBar);
// Grid
var grid = document.createElement('div');
grid.className = 'vg-grid';
grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(' + itemSize + 'px, 1fr))';
root.appendChild(grid);
// Pager
var pager = document.createElement('div');
pager.className = 'vg-pager';
pager.style.display = 'none';
root.appendChild(pager);
// Leer
var emptyEl = document.createElement('p');
emptyEl.className = 'vg-empty';
emptyEl.style.display = 'none';
emptyEl.textContent = '📷 Noch keine Bilder – als Admin einloggen und hochladen.';
root.appendChild(emptyEl);
// ── Lightbox ──────────────────────────────────────────────
var lb = document.createElement('div');
lb.style.cssText = 'display:none;position:fixed;top:0;left:0;width:100%;height:100%;z-index:999999;align-items:center;justify-content:center;';
var lbBg = document.createElement('div'); lbBg.id = 'vg-lightbox-bg';
var lbBgColor = sec.dataset.lbbgcolor || '#000000';
var lbBgOpacity = parseInt(sec.dataset.lbbgopacity || '90', 10) / 100;
var lbEffect = sec.dataset.lbeffect || 'fade';
// Hintergrundfarbe als rgba
var lbBgR = parseInt(lbBgColor.slice(1,3)||'00',16);
var lbBgG = parseInt(lbBgColor.slice(3,5)||'00',16);
var lbBgB = parseInt(lbBgColor.slice(5,7)||'00',16);
lbBg.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;background:rgba('+lbBgR+','+lbBgG+','+lbBgB+','+lbBgOpacity+');';
// Blur direkt aus data-Attribut lesen
var lbBlurPx = parseInt(sec.dataset.lblur || '0', 10);
if (lbBlurPx > 0) {
lbBg.style.backdropFilter = 'blur(' + lbBlurPx + 'px)';
lbBg.style.webkitBackdropFilter = 'blur(' + lbBlurPx + 'px)';
}
lb.appendChild(lbBg);
var bs = 'position:fixed;background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.25);color:#fff;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:1000000;font-size:1.1rem;';
var lbCloseBtn = document.createElement('button'); lbCloseBtn.style.cssText = bs+'top:1rem;right:1rem;width:40px;height:40px;'; lbCloseBtn.textContent='✕'; lb.appendChild(lbCloseBtn);
var lbPrevBtn = document.createElement('button'); lbPrevBtn.style.cssText = bs+'top:50%;left:1rem;width:48px;height:48px;font-size:1.75rem;transform:translateY(-50%);'; lbPrevBtn.textContent='‹'; lb.appendChild(lbPrevBtn);
var lbNextBtn = document.createElement('button'); lbNextBtn.style.cssText = bs+'top:50%;right:1rem;width:48px;height:48px;font-size:1.75rem;transform:translateY(-50%);'; lbNextBtn.textContent='›'; lb.appendChild(lbNextBtn);
var lbWrap = document.createElement('div');
lbWrap.style.cssText='position:relative;z-index:999999;display:flex;flex-direction:column;align-items:center;gap:0.75rem;';
var lbImg = document.createElement('img');
lbImg.alt='';
var lbBorderStyle = '';
if (showBorder) {
var bw = (sec.dataset.borderw || '2').replace('px','');
var bc = sec.dataset.borderc || '#1d9e75';
lbBorderStyle = 'border:' + bw + 'px solid ' + bc + ';';
}
lbImg.style.cssText = 'max-width:90vw;max-height:82vh;object-fit:contain;border-radius:6px;display:block;' + lbBorderStyle;
var lbCap = document.createElement('p');
lbCap.style.cssText='color:rgba(255,255,255,0.85);font-size:0.9rem;margin:0;text-align:center;max-width:80vw;';
lbWrap.appendChild(lbImg);
lbWrap.appendChild(lbCap);
lb.appendChild(lbWrap);
var lbCtr = document.createElement('div');
lbCtr.style.cssText='position:fixed;bottom:1rem;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.6);color:rgba(255,255,255,0.7);font-size:0.78rem;padding:0.3rem 0.9rem;border-radius:99px;z-index:1000000;';
lb.appendChild(lbCtr);
document.body.appendChild(lb);
// Login-Modal
var modal = document.createElement('div');
modal.style.cssText='display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);z-index:999998;align-items:center;justify-content:center;';
modal.innerHTML =
'<div style="background:#1a1a1a;border-radius:16px;padding:2rem;width:100%;max-width:360px;margin:1rem;box-shadow:0 24px 64px rgba(0,0,0,0.5);display:flex;flex-direction:column;gap:0.75rem;color:#cccccc;">'
+ '<h3 id="vg-modal-title-'+gid+'" style="color:#e0e0e0;font-size:1.1rem;font-weight:700;margin:0;text-align:center;">🔐 Admin-Login</h3>'
+ '<div id="vg-setup-wrap-'+gid+'" style="display:none;flex-direction:column;gap:0.5rem;">'
+ '<input type="password" id="vg-setup-pw-'+gid+'" style="background:#111111;border:1px solid #444;border-radius:8px;padding:0.7rem 1rem;color:#dddddd;font-size:0.9rem;width:100%;box-sizing:border-box;outline:none;margin-bottom:0.4rem;" placeholder="Passwort festlegen (min. 6)"/>'
+ '<input type="password" id="vg-setup-pw2-'+gid+'" style="background:#111111;border:1px solid #444;border-radius:8px;padding:0.7rem 1rem;color:#dddddd;font-size:0.9rem;width:100%;box-sizing:border-box;outline:none;margin-bottom:0.4rem;" placeholder="Passwort bestätigen"/>'
+ '</div>'
+ '<div id="vg-login-wrap-'+gid+'"><input type="password" id="vg-login-pw-'+gid+'" class="vg-input" placeholder="Passwort"/></div>'
+ '<div id="vg-modal-err-'+gid+'" style="display:none;background:rgba(231,76,60,0.12);border:1px solid rgba(231,76,60,0.3);border-radius:8px;padding:0.6rem;color:#e74c3c;font-size:0.82rem;"></div>'
+ '<div style="display:flex;gap:0.5rem;">'
+ '<button id="vg-modal-submit-'+gid+'" style="flex:1;padding:0.7rem;background:#1d9e75;color:#ffffff;border:none;border-radius:8px;font-size:0.9rem;font-weight:700;cursor:pointer;">Anmelden</button>'
+ '<button id="vg-modal-cancel-'+gid+'" style="flex:1;padding:0.7rem;background:transparent;color:#aaaaaa;border:1px solid #666;border-radius:8px;font-size:0.9rem;cursor:pointer;">Abbrechen</button>'
+ '</div></div>';
document.body.appendChild(modal);
// ── Render ────────────────────────────────────────────────
function filteredImages() {
if (activeCat === null) return [];
if (!activeCat) return images;
return images.filter(function(img){ return img.cat === activeCat; });
}
function renderFilter() {
if (!categories.length) {
filterBar.style.display='none';
return;
}
filterBar.style.display = 'flex';
filterBar.innerHTML = '';
function fb(label, cat) {
var b = document.createElement('button');
b.className = 'vg-filter-btn' + (activeCat===cat?' vg-filter-active':'');
b.textContent = label;
b.onclick = function(){
activeCat=cat;
curPage=0;
renderFilter();
renderPage();
};
filterBar.appendChild(b);
}
if (showAllBtn) fb('Alle', '');
categories.forEach(function(c){ fb(c.name, c.id); });
}
function renderPage() {
grid.innerHTML = '';
// Noch keine Kategorie gewählt → nichts zeigen
if (activeCat === null) {
emptyEl.style.display='none';
pager.style.display='none';
return;
}
var subset = filteredImages();
if (!subset.length) {
emptyEl.style.display='block';
pager.style.display='none';
return;
}
emptyEl.style.display = 'none';
var total = Math.ceil(subset.length / perPage);
if (curPage >= total) curPage = Math.max(0, total-1);
var start = curPage * perPage;
var slice = subset.slice(start, start + perPage);
slice.forEach(function(img, pi) {
var globalIdx = images.indexOf(img);
var item = document.createElement('div');
item.className='vg-item';
var inner = document.createElement('div');
inner.className='vg-item-inner';
if (showBorder) {
var bw = (sec.dataset.borderw || '2').replace('px','');
var bc = sec.dataset.borderc || '#1d9e75';
// Outer box-shadow auf item - NICHT auf inner (wäre von overflow:hidden geclipt)
item.style.boxShadow = '0 0 0 ' + bw + 'px ' + bc;
}
var imgEl = document.createElement('img');
imgEl.className='vg-img';
imgEl.alt=img.caption||'';
imgEl.setAttribute('loading','lazy');
imgEl.src=img.url;
var ov = document.createElement('div');
ov.className='vg-overlay';
// Zoom Button
var zb = document.createElement('button');
zb.className='vg-icon-btn';
zb.textContent='🔍';
zb.onclick=(function(idx){
return function(e){
e.stopPropagation();
lbOpen(idx);
};
})(start+pi);
ov.appendChild(zb);
if (isAdmin) {
// Kategorie-Zuordnung
var catBtn = document.createElement('button');
catBtn.className='vg-icon-btn';
catBtn.textContent='📁';
catBtn.title='Kategorie zuordnen';
catBtn.onclick=(function(imgRef){
return function(e){
e.stopPropagation();
if (!categories.length) {
alert('Erst Kategorien anlegen!');
return;
}
var opts = categories.map(function(c){ return c.id+'|'+c.name; });
var cur = categories.findIndex(function(c){ return c.id===imgRef.cat; });
var sel = prompt(
'Kategorie wählen:\n' +
categories.map(function(c,i){ return (i+1)+'. '+c.name; }).join('\n') +
'\n0. Keine',
cur>=0?cur+1:0
);
if (sel===null) return;
var idx = parseInt(sel,10)-1;
var catId = (idx>=0&&idx<categories.length) ? categories[idx].id : '';
post('setcat',{name:imgRef.name,cat:catId},function(d){
if(d.success){
imgRef.cat=catId;
renderPage();
}
});
};
})(img);
ov.appendChild(catBtn);
// Löschen
var db = document.createElement('button');
db.className='vg-icon-btn vg-del-btn';
db.textContent='🗑';
db.onclick=(function(imgRef){
return function(e){
e.stopPropagation();
if (!confirm('Bild löschen?')) return;
post('delete',{name:imgRef.name},function(d){
if(d.success){
images=images.filter(function(x){return x.name!==imgRef.name;});
renderFilter();
renderPage();
} else {
alert(d.message);
}
});
};
})(img);
ov.appendChild(db);
}
inner.appendChild(imgEl);
inner.appendChild(ov);
item.appendChild(inner);
item.onclick=(function(i){
return function(){
lbOpen(i);
};
})(start+pi);
// Caption
if (img.caption || isAdmin) {
var cap = document.createElement('div');
cap.className='vg-caption'+((!img.caption&&isAdmin)?' vg-caption-add':'');
cap.textContent=img.caption||(isAdmin?'+ Beschriftung':'');
if (isAdmin) {
cap.style.cursor='pointer';
cap.onclick=(function(imgRef,capEl){
return function(e){
e.stopPropagation();
var neu=prompt('Beschriftung:',imgRef.caption||'');
if(neu===null)return;
post('caption',{name:imgRef.name,caption:neu},function(d){
if(d.success){
imgRef.caption=neu;
capEl.textContent=neu||'+ Beschriftung';
capEl.className='vg-caption'+(!neu?' vg-caption-add':'');
}
});
};
})(img,cap);
}
item.appendChild(cap);
}
grid.appendChild(item);
});
// Paginierung
if (total > 1) {
pager.style.display='flex';
pager.innerHTML='';
var prevBtn=document.createElement('button');
prevBtn.className='vg-page-btn';
prevBtn.textContent='‹';
prevBtn.disabled=curPage===0;
prevBtn.onclick=function(){
curPage--;
renderPage();
root.scrollIntoView({behavior:'smooth'});
};
pager.appendChild(prevBtn);
for (var p=0;p<total;p++) {
var pgb=document.createElement('button');
pgb.className='vg-page-btn'+(p===curPage?' vg-page-active':'');
pgb.textContent=p+1;
pgb.onclick=(function(pg){
return function(){
curPage=pg;
renderPage();
root.scrollIntoView({behavior:'smooth'});
};
})(p);
pager.appendChild(pgb);
}
var nextBtn=document.createElement('button');
nextBtn.className='vg-page-btn';
nextBtn.textContent='›';
nextBtn.disabled=curPage>=total-1;
nextBtn.onclick=function(){
curPage++;
renderPage();
root.scrollIntoView({behavior:'smooth'});
};
pager.appendChild(nextBtn);
var info=document.createElement('span');
info.className='vg-page-info';
info.textContent=(start+1)+'–'+Math.min(start+perPage,subset.length)+' von '+subset.length;
pager.appendChild(info);
} else {
pager.style.display='none';
}
}
function loadGallery() {
get('list',function(d){
if(!d.success) return;
images = d.images || [];
categories = d.categories || [];
curPage = 0;
// STARTKATEGORIE MIT MOBIRISE-SCHALTER:
// Erst jetzt sind die Kategorien bekannt.
// Wenn der Mobirise-Schalter EIN ist und im neuen Feld eine Kategorie steht,
// wird diese Kategorie einmalig aktiviert.
// Wenn das neue Feld leer ist, werden alle Bilder angezeigt.
// Wenn der Mobirise-Schalter AUS ist, bleibt activeCat auf null und es erscheinen keine Bilder.
applyInitialCategory();
renderFilter();
renderPage();
fillCatSelect();
renderCatAdmin();
});
}
// ── Kategorien-Admin ──────────────────────────────────────
function fillCatSelect() {
var sel = document.getElementById('vg-cat-select-'+gid);
if (!sel) return;
sel.innerHTML = '<option value>– Keine Kategorie –</option>'
+ categories.map(function(c){
return '<option value="'+c.id+'">'+c.name+'</option>';
}).join('');
}
function renderCatAdmin() {
var list = document.getElementById('vg-cat-list-'+gid);
if (!list) return;
if (!categories.length) {
list.innerHTML='<p style="color:#555;font-size:0.82rem;margin:0;">Noch keine Kategorien.</p>';
return;
}
list.innerHTML = categories.map(function(c){
return '<div class="vg-cat-row"><span>'+c.name+'</span>'
+ '<button class="vg-del-cat-btn" data-id="'+c.id+'">✕</button></div>';
}).join('');
list.querySelectorAll('.vg-del-cat-btn').forEach(function(btn){
btn.onclick=function(){
if(!confirm('Kategorie "'+btn.parentElement.querySelector('span').textContent+'" löschen?'))return;
post('deletecat',{id:btn.dataset.id},function(d){
if(d.success){
categories=d.categories;
renderCatAdmin();
fillCatSelect();
renderFilter();
renderPage();
}
});
};
});
}
// ── Lightbox ──────────────────────────────────────────────
var lbIdx=0, lbSubset=[];
function lbOpen(i) {
lbSubset = filteredImages();
lbIdx=i;
if (!lbSubset.length || !lbSubset[lbIdx]) return;
// Schritt 1: sofort unsichtbar machen (ohne Transition)
lb.style.transition = 'none';
lb.style.opacity = '0';
// Schritt 2: sichtbar schalten
lb.style.display = 'flex';
lb.style.alignItems = 'center';
lb.style.justifyContent = 'center';
lbUpdate();
document.body.style.overflow = 'hidden';
// Schritt 3: Transition aktivieren + einblenden
setTimeout(function(){
lb.style.transition = 'opacity 0.8s ease';
lb.style.opacity = '1';
}, 50);
}
function lbClose(){
lb.style.transition = 'opacity 0.25s ease';
lb.style.opacity = '0';
setTimeout(function(){
lb.style.display = 'none';
lb.style.transition = '';
lb.style.opacity = '1';
document.body.style.overflow = '';
}, 270);
}
function lbPrev() {
lbIdx=(lbIdx-1+lbSubset.length)%lbSubset.length;
lbUpdateAnim(-1);
}
function lbNext() {
lbIdx=(lbIdx+1)%lbSubset.length;
lbUpdateAnim(1);
}
function lbUpdateAnim(dir) {
var item = lbSubset[lbIdx];
if (!item) return;
if (lbEffect === 'slide') {
lbImg.style.transition = 'none';
lbImg.style.transform = 'translateX(' + (dir > 0 ? '60px' : '-60px') + ')';
lbImg.style.opacity = '0';
setTimeout(function(){
lbImg.src = item.url;
lbImg.style.transition = 'transform 0.35s ease, opacity 0.35s ease';
lbImg.style.transform = 'translateX(0)';
lbImg.style.opacity = '1';
}, 30);
} else if (lbEffect === 'zoom') {
lbImg.style.transition = 'none';
lbImg.style.transform = 'scale(0.85)';
lbImg.style.opacity = '0';
setTimeout(function(){
lbImg.src = item.url;
lbImg.style.transition = 'transform 0.35s ease, opacity 0.35s ease';
lbImg.style.transform = 'scale(1)';
lbImg.style.opacity = '1';
}, 30);
} else {
// Fade (default)
lbImg.style.transition = 'opacity 0.3s ease';
lbImg.style.opacity = '0';
setTimeout(function(){
lbImg.src = item.url;
lbImg.style.opacity = '1';
}, 160);
}
if(lbCap) lbCap.textContent = item.caption || '';
if(lbCtr) lbCtr.textContent = (lbIdx+1) + ' / ' + lbSubset.length;
}
function lbUpdate(){
if (!lbSubset.length || !lbSubset[lbIdx]) return;
if(lbImg) lbImg.src=lbSubset[lbIdx].url;
if(lbCap) lbCap.textContent=lbSubset[lbIdx].caption||'';
if(lbCtr) lbCtr.textContent=(lbIdx+1)+' / '+lbSubset.length;
}
lbCloseBtn.onclick=lbClose;
lbPrevBtn.onclick=lbPrev;
lbNextBtn.onclick=lbNext;
lbBg.onclick=lbClose;
document.addEventListener('keydown',function(e){
if(lb.style.display==='none')return;
if(e.key==='Escape')lbClose();
if(e.key==='ArrowLeft')lbPrev();
if(e.key==='ArrowRight')lbNext();
});
var tx=0;
lb.addEventListener('touchstart',function(e){
tx=e.touches[0].clientX;
});
lb.addEventListener('touchend',function(e){
var dx=e.changedTouches[0].clientX-tx;
if(dx>50)lbPrev();
if(dx<-50)lbNext();
});
// ── Upload ────────────────────────────────────────────────
var fileInput=document.getElementById('vg-file-input-'+gid);
var dropArea=upZone.querySelector('.vg-drop-area');
if(dropArea){
['dragenter','dragover'].forEach(function(ev){
dropArea.addEventListener(ev,function(e){
e.preventDefault();
e.stopPropagation();
});
});
dropArea.addEventListener('drop',function(e){
e.preventDefault();
e.stopPropagation();
if(e.dataTransfer.files.length)doUpload(e.dataTransfer.files);
});
}
if(fileInput) fileInput.onchange=function(){
doUpload(this.files);
};
var upBtn=document.getElementById('vg-upload-btn-'+gid);
if(upBtn) upBtn.onclick=function(){
var fi=document.getElementById('vg-file-input-'+gid);
if(fi&&fi.files&&fi.files.length) {
doUpload(fi.files);
} else {
fi&&fi.click();
}
};
function doUpload(files){
if(!files||!files.length)return;
var pw=document.getElementById('vg-prog-wrap-'+gid);
var pb=document.getElementById('vg-prog-bar-'+gid);
var pt=document.getElementById('vg-prog-txt-'+gid);
var cap=(document.getElementById('vg-cap-input-'+gid)||{}).value||'';
var cat=(document.getElementById('vg-cat-select-'+gid)||{}).value||'';
if(pw)pw.style.display='block';
var fd=new FormData();
fd.append('action','upload');
fd.append('gid',gid);
fd.append('caption',cap);
fd.append('cat',cat);
for(var i=0;i<files.length;i++) fd.append('images[]',files[i]);
var xhr=new XMLHttpRequest();
xhr.open('POST',ep,true);
xhr.upload.onprogress=function(e){
if(e.lengthComputable&&pb)pb.style.width=Math.round(e.loaded/e.total*100)+'%';
};
xhr.onload=function(){
try{
var d=JSON.parse(xhr.responseText);
if(pt)pt.textContent=d.message||'Fertig!';
setTimeout(function(){
if(pw)pw.style.display='none';
if(pb)pb.style.width='0%';
if(fileInput)fileInput.value='';
var ci=document.getElementById('vg-cap-input-'+gid);
if(ci)ci.value='';
var dt=document.getElementById('vg-drop-txt-'+gid);
if(dt)dt.textContent='Bilder hier reinziehen oder klicken';
loadGallery();
},1200);
if(d.errors&&d.errors.length)alert('Fehler:\n'+d.errors.join('\n'));
}catch(e){}
};
xhr.send(fd);
}
// ── Admin / Login ─────────────────────────────────────────
// 4-Fach Tipp für Handy
var tapCount=0, tapTimer=null;
sec.addEventListener('touchend',function(){
tapCount++;
if(tapTimer)clearTimeout(tapTimer);
tapTimer=setTimeout(function(){
tapCount=0;
},1500);
if(tapCount>=4){
tapCount=0;
clearTimeout(tapTimer);
showModal();
}
});
// Strg+Doppelklick für Desktop
sec.addEventListener('dblclick',function(e){
if(e.ctrlKey)showModal();
});
function showModal(){
modal.style.display='flex';
modal.style.alignItems='center';
modal.style.justifyContent='center';
var title=document.getElementById('vg-modal-title-'+gid);
var sw=document.getElementById('vg-setup-wrap-'+gid);
var lw=document.getElementById('vg-login-wrap-'+gid);
var sub=document.getElementById('vg-modal-submit-'+gid);
if(firstRun){
if(title)title.textContent='⚙️ Erst-Einrichtung ('+gid+')';
if(sw){
sw.style.display='flex';
sw.style.flexDirection='column';
sw.style.gap='0.5rem';
}
if(lw)lw.style.display='none';
if(sub)sub.textContent='Einrichten';
}else{
if(title)title.textContent='🔐 Admin-Login ('+gid+')';
if(sw)sw.style.display='none';
if(lw)lw.style.display='block';
if(sub)sub.textContent='Anmelden';
setTimeout(function(){
var pw=document.getElementById('vg-login-pw-'+gid);
if(pw)pw.focus();
},100);
}
}
function hideModal(){
modal.style.display='none';
var err=document.getElementById('vg-modal-err-'+gid);
if(err)err.style.display='none';
}
function doLogin(){
var err=document.getElementById('vg-modal-err-'+gid);
if(firstRun){
var pw=(document.getElementById('vg-setup-pw-'+gid)||{}).value||'';
var pw2=(document.getElementById('vg-setup-pw2-'+gid)||{}).value||'';
post('setup',{password:pw,password2:pw2},function(d){
if(d.success){
hideModal();
setAdmin(true);
}else{
if(err){
err.textContent=d.message;
err.style.display='block';
}
}
});
}else{
var pw=(document.getElementById('vg-login-pw-'+gid)||{}).value||'';
post('login',{password:pw},function(d){
if(d.success){
hideModal();
setAdmin(true);
}else{
if(err){
err.textContent=d.message;
err.style.display='block';
}
}
});
}
}
function setAdmin(on){
isAdmin=on;
var bar=document.getElementById('vg-admin-bar-'+gid);
if(bar)bar.style.display=on?'flex':'none';
if(!on){
upZone.style.display='none';
catPanel.style.display='none';
}
renderPage();
}
document.getElementById('vg-modal-submit-'+gid).onclick=doLogin;
document.getElementById('vg-modal-cancel-'+gid).onclick=hideModal;
var lpw=document.getElementById('vg-login-pw-'+gid);
if(lpw)lpw.onkeypress=function(e){
if(e.key==='Enter')doLogin();
};
document.getElementById('vg-logout-btn-'+gid).onclick=function(){
post('logout',{},function(){
setAdmin(false);
});
};
document.getElementById('vg-up-toggle-'+gid).onclick=function(){
upZone.style.display=upZone.style.display==='none'?'block':'none';
};
document.getElementById('vg-cat-toggle-'+gid).onclick=function(){
catPanel.style.display=catPanel.style.display==='none'?'block':'none';
};
document.getElementById('vg-add-cat-'+gid).onclick=function(){
var inp=document.getElementById('vg-new-cat-'+gid);
if(!inp||!inp.value.trim())return;
post('addcat',{name:inp.value.trim()},function(d){
if(d.success){
categories=d.categories;
inp.value='';
renderCatAdmin();
fillCatSelect();
renderFilter();
}else{
alert(d.message);
}
});
};
// ── Start ─────────────────────────────────────────────────
get('check',function(d){
firstRun=!!d.firstRun;
if(d.loggedIn)setAdmin(true);
loadGallery();
});
})();
</script>
</section>Re: Galerie Block mit PHP
Bau ich heute Abend ein Tommy.
Doch einen Fehler gefunden
Alles andere hätte mich auch gewundert 
Das könnte ich dann im Video zeigen wie man das korrigiert und die mbrext neu macht
Doch einen Fehler gefunden
Das könnte ich dann im Video zeigen wie man das korrigiert und die mbrext neu macht
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
Ich glaube das ist auch echt gut. Denn bestimmt gibt es Leute die mit einem Favoriten beginnen wollen, der vielleicht erst nach Monaten oder Jahren erst zur Galerie dazu kommt.
Gib mit bitte Bescheid, dann lade ich die neue .mbrext nochmals runter, damit ich das dann auch so hier importieren kann.
Gib mit bitte Bescheid, dann lade ich die neue .mbrext nochmals runter, damit ich das dann auch so hier importieren kann.
Re: Galerie Block mit PHP
Ja ich mach das dann jetzt mal im Bus 

Die alte Schlampe - hab sie direkt gerügtIch weiß es nicht sicher – aber die Kommentare im Code sind auf Deutsch, sehr ausführlich erklärt, und der Stil erinnert mich an die Art wie Tommy Code dokumentiert. Außerdem habt ihr ja schon öfter zusammengearbeitet.
War es jemand anderes?
Re: Galerie Block mit PHP
Lad mal runter Tommy ist online jetzt
- Tommy Herrmann
- Site Admin

- Beiträge: 9139
- Registriert: So 6. Dez 2020, 07:37
- Wohnort: Berlin
- Kontaktdaten:
Re: Galerie Block mit PHP
OK - ich gucke dann aber nur in den HTML-Block und mache nichts anderes. Ich gehe davon aus, das da nichts im Download fehlt.
Ich gebe dann gleich Bescheid.
Ich gebe dann gleich Bescheid.
Wer ist online?
Mitglieder in diesem Forum: 0 Mitglieder und 2 Gäste
