Mit Formular
This commit is contained in:
BIN
mod_pdf_tree/.DS_Store
vendored
BIN
mod_pdf_tree/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
@@ -39,9 +39,12 @@ class ModEisAnzeigeHelper
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hauptfunktion zum Rendern des Baums
|
* Hauptfunktion zum Rendern des Baums
|
||||||
|
* - Nutzt title als alternativen Anzeigenamen (Fallback name)
|
||||||
|
* - Fügt für Ordner eine klickbare Label-Zeile hinzu (bessere Tap-Ziele auf iPad)
|
||||||
|
* - Liefert data-* Attribute für spätere Inline-Edits / Preview
|
||||||
*/
|
*/
|
||||||
public static function renderTree(array $items, int $parentId = null): string
|
public static function renderTree(array $items, ?int $parentId = null): string
|
||||||
{
|
{
|
||||||
if (!isset($items[$parentId])) {
|
if (!isset($items[$parentId])) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -54,16 +57,20 @@ class ModEisAnzeigeHelper
|
|||||||
$fileId = (int) $item['id'];
|
$fileId = (int) $item['id'];
|
||||||
|
|
||||||
// Dateiendung .pdf entfernen und escapen
|
// Dateiendung .pdf entfernen und escapen
|
||||||
$displayName = preg_replace('/\.pdf$/i', '', $rawName);
|
$displayName = preg_replace('/\.pdf$/i', '', (string) $rawName);
|
||||||
$displayName = htmlspecialchars($displayName, ENT_QUOTES, 'UTF-8');
|
$displayName = htmlspecialchars($displayName, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
if ($isFolder) {
|
if ($isFolder) {
|
||||||
$fileCount = self::countFilesRecursive($items, $item['id']);
|
$fileCount = self::countFilesRecursive($items, $item['id']);
|
||||||
|
|
||||||
// WICHTIG: Toggle-Element einfügen – dein JS hängt hier dran
|
// Ordner-<li> inkl. Data-Attribute + große Tap-Ziele (toggle + folder-label)
|
||||||
$html .= '<li class="folder">';
|
$html .= '<li class="folder"'
|
||||||
|
. ' data-id="' . (int) $item['id'] . '"'
|
||||||
|
. ' data-title="' . htmlspecialchars((string)($item['title'] ?? ''), ENT_QUOTES, 'UTF-8') . '"'
|
||||||
|
. ' data-name="' . htmlspecialchars((string)$item['name'], ENT_QUOTES, 'UTF-8') . '"'
|
||||||
|
. '>';
|
||||||
$html .= '<span class="toggle" role="button" aria-label="Ordner umschalten" tabindex="0">▶</span> ';
|
$html .= '<span class="toggle" role="button" aria-label="Ordner umschalten" tabindex="0">▶</span> ';
|
||||||
$html .= '📁 ' . $displayName . ' <small>(' . (int) $fileCount . ')</small>';
|
$html .= '<span class="folder-label">📁 ' . $displayName . ' <small class="count">(' . (int) $fileCount . ')</small></span>';
|
||||||
|
|
||||||
// Kindknoten
|
// Kindknoten
|
||||||
$html .= self::renderTree($items, $item['id']);
|
$html .= self::renderTree($items, $item['id']);
|
||||||
@@ -71,7 +78,7 @@ class ModEisAnzeigeHelper
|
|||||||
} else {
|
} else {
|
||||||
$link = Route::_('index.php?option=com_eis&task=download.download&id=' . $fileId);
|
$link = Route::_('index.php?option=com_eis&task=download.download&id=' . $fileId);
|
||||||
|
|
||||||
// Tooltip + (neu) inline-Anzeige der Dateigröße, falls der Pfad existiert
|
// Tooltip + (optional) inline-Anzeige der Dateigröße
|
||||||
$tooltip = '';
|
$tooltip = '';
|
||||||
$sizeStr = '';
|
$sizeStr = '';
|
||||||
if (!empty($item['path']) && is_file($item['path'])) {
|
if (!empty($item['path']) && is_file($item['path'])) {
|
||||||
@@ -79,21 +86,27 @@ class ModEisAnzeigeHelper
|
|||||||
if ($size !== false) {
|
if ($size !== false) {
|
||||||
$formatted = self::formatFileSize((int) $size);
|
$formatted = self::formatFileSize((int) $size);
|
||||||
$tooltip = ' title="Größe: ' . $formatted . '"';
|
$tooltip = ' title="Größe: ' . $formatted . '"';
|
||||||
// Sichtbar neben dem Dateinamen anzeigen
|
// Wenn du die Größe auch sichtbar möchtest, Zeile darunter einkommentieren:
|
||||||
//$sizeStr = ' <small class="meta">(' . $formatted . ')</small>';
|
// $sizeStr = ' <small class="meta">(' . $formatted . ')</small>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= '<li class="file">';
|
// Datei-<li> inkl. Data-Attribute + data-filename am Link (für Preview-Anzeige)
|
||||||
//$html .= '📄 <a class="file-link" href="' . $link . '"' . $tooltip . '>' . $displayName . '</a>' . $sizeStr;
|
$html .= '<li class="file"'
|
||||||
$html .= '📄 <a class="file-link" href="' . $link . '"' . $tooltip . '>' . $displayName . '</a>';
|
. ' data-id="' . $fileId . '"'
|
||||||
|
. ' data-title="' . htmlspecialchars((string)($item['title'] ?? ''), ENT_QUOTES, 'UTF-8') . '"'
|
||||||
|
. ' data-name="' . htmlspecialchars((string)$item['name'], ENT_QUOTES, 'UTF-8') . '"'
|
||||||
|
. '>';
|
||||||
|
$html .= '📄 <a class="file-link" href="' . $link . '"' . $tooltip
|
||||||
|
. ' data-filename="' . $displayName . '">'
|
||||||
|
. $displayName . '</a>' . $sizeStr;
|
||||||
$html .= '</li>';
|
$html .= '</li>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= '</ul>';
|
$html .= '</ul>';
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zählt alle PDF-Dateien unterhalb eines Ordners rekursiv
|
* Zählt alle PDF-Dateien unterhalb eines Ordners rekursiv
|
||||||
@@ -108,7 +121,7 @@ class ModEisAnzeigeHelper
|
|||||||
|
|
||||||
foreach ($items[$parentId] as $item) {
|
foreach ($items[$parentId] as $item) {
|
||||||
if ((bool) $item['is_folder']) {
|
if ((bool) $item['is_folder']) {
|
||||||
$count += self::countFilesRecursive($items, $item['id']);
|
$count += self::countFilesRecursive($items, (int) $item['id']);
|
||||||
} else {
|
} else {
|
||||||
$count++;
|
$count++;
|
||||||
}
|
}
|
||||||
@@ -118,7 +131,7 @@ class ModEisAnzeigeHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formatierte Dateigröße für Tooltip
|
* Formatierte Dateigröße für Tooltip / Anzeige
|
||||||
*/
|
*/
|
||||||
private static function formatFileSize(int $bytes): string
|
private static function formatFileSize(int $bytes): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,83 +1,141 @@
|
|||||||
<?php defined('_JEXEC') or die; ?>
|
<?php defined('_JEXEC') or die; ?>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* =========================
|
||||||
|
Layout-Container
|
||||||
|
========================= */
|
||||||
.pdf-tree-wrapper {
|
.pdf-tree-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: stretch; /* wichtig für scrollende Kinder */
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
|
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
|
||||||
|
min-height: 0; /* erlaubt Overflow in Flex-Kindern */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Linke Spalte: Baum */
|
||||||
.pdf-tree-container {
|
.pdf-tree-container {
|
||||||
flex: 0 0 30%;
|
flex: 0 0 30%;
|
||||||
max-height: 95vh;
|
max-height: calc(var(--vh, 100vh) - 160px); /* robust auf Desktop/iPad */
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; /* smooth scroll iOS */
|
||||||
|
min-height: 0; /* wichtig für Scrollbar */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rechte Spalte: Vorschau */
|
||||||
#pdf-preview {
|
#pdf-preview {
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
min-width: 800px;
|
min-width: 320px; /* iPad-freundlicher als 800px */
|
||||||
height: 95vh;
|
height: calc(var(--vh, 100vh) - 160px);
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: none;
|
display: none; /* wird erst bei Klick sichtbar */
|
||||||
|
min-height: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Preview-Toolbar / Fallback-Link */
|
||||||
|
.preview-actions {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5rem;
|
||||||
|
padding: .5rem .75rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.preview-actions a,
|
||||||
|
.preview-actions button {
|
||||||
|
font: inherit;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: .35rem .6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iFrame */
|
||||||
#pdf-preview iframe {
|
#pdf-preview iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100% - 42px); /* Platz für .preview-actions */
|
||||||
border: none;
|
border: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.pdf-tree {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.pdf-tree ul {
|
|
||||||
list-style: none;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
padding: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-tree li {
|
/* =========================
|
||||||
margin: 0.3em 0;
|
Baum-Liste
|
||||||
}
|
========================= */
|
||||||
|
.pdf-tree { list-style: none; margin: 0; padding: 0; }
|
||||||
|
.pdf-tree ul { list-style: none; margin-left: 1.25em; padding: 0; display: none; }
|
||||||
|
.pdf-tree li { margin: 0.35em 0; }
|
||||||
|
|
||||||
|
/* Touch-freundliche Tap-Ziele */
|
||||||
.folder > .toggle {
|
.folder > .toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 1em;
|
width: 1.5em; /* größer für Finger */
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
margin-right: .1em;
|
||||||
}
|
}
|
||||||
|
.folder > .folder-label { /* gesamten Text klickbar machen */
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: .15rem .1rem; /* kleine Hitbox-Polster */
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.folder > .folder-label:active { background: rgba(0,0,0,0.05); }
|
||||||
|
|
||||||
.pdf-tree .meta { color: #666; }
|
.pdf-tree .meta { color: #666; }
|
||||||
|
|
||||||
/* Optionale visuelle Trennung bei Dateien */
|
/* Datei-Link */
|
||||||
.file-link {
|
.file-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
}
|
}
|
||||||
.file-link:hover {
|
.file-link:hover { text-decoration: underline; }
|
||||||
text-decoration: underline;
|
.file-link:active { opacity: .7; }
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
.expand-collapse {
|
.expand-collapse {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.expand-collapse button {
|
.expand-collapse button {
|
||||||
margin-right: 0.5em;
|
margin-right: 0; /* via gap */
|
||||||
|
font: inherit;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: .4rem .6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar-Optik (optional) */
|
||||||
|
.pdf-tree-container::-webkit-scrollbar { width: 10px; }
|
||||||
|
.pdf-tree-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,.2); border-radius: 6px; }
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Responsive: iPad Portrait / schmal
|
||||||
|
========================= */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.pdf-tree-wrapper { flex-direction: column; }
|
||||||
|
.pdf-tree-container,
|
||||||
|
#pdf-preview {
|
||||||
|
max-height: calc(var(--vh, 100vh) - 220px);
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Buttons: Aus-/Einklappen -->
|
<!-- Buttons: Aus-/Einklappen -->
|
||||||
<div class="expand-collapse">
|
<div class="expand-collapse">
|
||||||
<button id="expand-all">ausklappen</button>
|
<button id="expand-all" type="button">ausklappen</button>
|
||||||
<button id="collapse-all">einklappen</button>
|
<button id="collapse-all" type="button">einklappen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PDF-Baum und Vorschau -->
|
<!-- PDF-Baum und Vorschau -->
|
||||||
@@ -85,49 +143,98 @@
|
|||||||
<div class="pdf-tree-container">
|
<div class="pdf-tree-container">
|
||||||
<?= ModEisAnzeigeHelper::renderTree($items); ?>
|
<?= ModEisAnzeigeHelper::renderTree($items); ?>
|
||||||
</div>
|
</div>
|
||||||
<div id="pdf-preview"></div>
|
|
||||||
|
<div id="pdf-preview">
|
||||||
|
<div class="preview-actions">
|
||||||
|
<span id="preview-filename" aria-live="polite"></span>
|
||||||
|
<a id="open-newtab" href="#" target="_blank" rel="noopener">in neuem Tab öffnen</a>
|
||||||
|
</div>
|
||||||
|
<!-- iFrame wird dynamisch eingesetzt -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
// --- echtes Viewport-Height-Polyfill (fix für Safari/macOS/iOS) ---
|
||||||
const preview = document.getElementById('pdf-preview');
|
function setRealVh() {
|
||||||
if (!preview) return;
|
const vh = window.innerHeight * 0.01;
|
||||||
|
document.documentElement.style.setProperty('--vh', `${vh * 100}px`);
|
||||||
|
}
|
||||||
|
setRealVh();
|
||||||
|
window.addEventListener('resize', setRealVh, { passive: true });
|
||||||
|
|
||||||
// (1) Verzeichnisse toggeln
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
document.querySelectorAll('.pdf-tree .toggle').forEach(toggle => {
|
const treeContainer = document.querySelector('.pdf-tree-container');
|
||||||
toggle.addEventListener('click', () => {
|
const preview = document.getElementById('pdf-preview');
|
||||||
const ul = toggle.closest('li').querySelector('ul');
|
if (!treeContainer || !preview) return;
|
||||||
if (ul) {
|
|
||||||
|
const previewBar = preview.querySelector('.preview-actions');
|
||||||
|
const openNewTab = document.getElementById('open-newtab');
|
||||||
|
const previewName = document.getElementById('preview-filename');
|
||||||
|
|
||||||
|
// --- (1) Verzeichnisse toggeln – Caret und Label (Event Delegation, robust für iPad) ---
|
||||||
|
treeContainer.addEventListener('click', (ev) => {
|
||||||
|
const t = ev.target;
|
||||||
|
|
||||||
|
// Toggle per Caret
|
||||||
|
if (t.classList.contains('toggle')) {
|
||||||
|
const li = t.closest('li.folder');
|
||||||
|
if (!li) return;
|
||||||
|
const ul = li.querySelector(':scope > ul');
|
||||||
|
if (!ul) return;
|
||||||
const visible = ul.style.display === 'block';
|
const visible = ul.style.display === 'block';
|
||||||
ul.style.display = visible ? 'none' : 'block';
|
ul.style.display = visible ? 'none' : 'block';
|
||||||
toggle.textContent = visible ? '▶' : '▼';
|
t.textContent = visible ? '▶' : '▼';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle per Ordner-Label
|
||||||
|
if (t.classList.contains('folder-label')) {
|
||||||
|
const li = t.closest('li.folder');
|
||||||
|
if (!li) return;
|
||||||
|
const caret = li.querySelector(':scope > .toggle');
|
||||||
|
if (caret) caret.click();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// (2) Alle ausklappen
|
// --- (2) Alle ausklappen (mit Scroll-to-top) ---
|
||||||
document.getElementById('expand-all')?.addEventListener('click', () => {
|
document.getElementById('expand-all')?.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'block');
|
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'block');
|
||||||
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▼');
|
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▼');
|
||||||
|
treeContainer.scrollTo({ top: 0, behavior: 'instant' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// (3) Alle einklappen
|
// --- (3) Alle einklappen ---
|
||||||
document.getElementById('collapse-all')?.addEventListener('click', () => {
|
document.getElementById('collapse-all')?.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'none');
|
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'none');
|
||||||
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▶');
|
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▶');
|
||||||
});
|
});
|
||||||
|
|
||||||
// (4) PDF-Vorschau
|
// --- (4) PDF-Vorschau + Fallback-Link (iOS: iframe kann zickig sein) ---
|
||||||
document.querySelectorAll('.pdf-tree .file-link').forEach(link => {
|
treeContainer.addEventListener('click', (ev) => {
|
||||||
link.addEventListener('click', e => {
|
const a = ev.target.closest('.file-link');
|
||||||
e.preventDefault();
|
if (!a) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
preview.style.display = 'block';
|
preview.style.display = 'block';
|
||||||
preview.innerHTML = ''; // Vorherige Vorschau entfernen
|
previewBar.style.display = 'flex';
|
||||||
|
previewName.textContent = a.getAttribute('data-filename') || a.textContent.trim();
|
||||||
|
openNewTab.href = a.href;
|
||||||
|
|
||||||
|
// iFrame neu setzen
|
||||||
|
preview.querySelector('iframe')?.remove();
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.src = link.href;
|
iframe.setAttribute('title', 'PDF-Vorschau');
|
||||||
|
iframe.src = a.href;
|
||||||
iframe.loading = 'lazy';
|
iframe.loading = 'lazy';
|
||||||
|
|
||||||
|
// iOS Fallback: Wenn Onload nicht in 1.5s triggert, Link oben verwenden
|
||||||
|
let loaded = false;
|
||||||
|
const t = setTimeout(() => { /* optional Hinweis einblenden */ }, 1500);
|
||||||
|
iframe.addEventListener('load', () => { loaded = true; clearTimeout(t); }, { once: true });
|
||||||
|
|
||||||
preview.appendChild(iframe);
|
preview.appendChild(iframe);
|
||||||
});
|
}, { passive: false });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user