feat: Seiten-Thumbnails mit echter Vorschau + Hell/Dunkel-Toggle
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Jeder Thumb hat jetzt einen mini canvas, der das Bild oder die erste
PDF-Seite gerendert anzeigt (max 200px)
- Papier-Look: A4 aspect-ratio (1:1.414), weißer Hintergrund per Default
- 🌓-Button im Pages-Header schaltet zwischen paper-light und paper-dark
- Toolbar-Inputs (select/number/checkbox): heller Text auf dunklem
Hintergrund für Dark-Theme
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
This commit is contained in:
parent
1d3315a0b5
commit
90d89130f3
3 changed files with 136 additions and 22 deletions
|
|
@ -313,11 +313,17 @@ if (!$bericht) {
|
|||
|
||||
// RECHTS: Seiten-Thumbnails
|
||||
print '<aside class="bericht-pages">';
|
||||
print '<div class="bericht-pages-header">';
|
||||
print '<h4>'.$langs->trans("BerichtPages").' (<span id="page-count">'.count($pages).'</span>)</h4>';
|
||||
print '<div id="bericht-page-list" class="page-list">';
|
||||
print '<button type="button" id="btn-toggle-thumb-bg" title="Hell/Dunkel">🌓</button>';
|
||||
print '</div>';
|
||||
print '<div id="bericht-page-list" class="page-list paper-light">';
|
||||
foreach ($pages as $idx => $p) {
|
||||
print '<div class="page-thumb" data-pageid="'.$p->id.'" data-order="'.$p->page_order.'">';
|
||||
print '<div class="page-thumb-inner"><span class="page-num">'.($idx + 1).'</span></div>';
|
||||
print '<div class="page-thumb-paper">';
|
||||
print '<canvas class="thumb-canvas"></canvas>';
|
||||
print '</div>';
|
||||
print '<div class="page-thumb-label"><span class="page-num">'.($idx + 1).'</span></div>';
|
||||
print '<div class="page-thumb-actions">';
|
||||
print '<button type="button" class="thumb-del" title="'.$langs->trans("BerichtDeletePage").'">🗑️</button>';
|
||||
print '</div>';
|
||||
|
|
|
|||
|
|
@ -102,16 +102,19 @@
|
|||
.bericht-toolbar select,
|
||||
.bericht-toolbar input[type="number"],
|
||||
.bericht-toolbar input[type="text"] {
|
||||
background: var(--inputbackgroundcolor, #fff);
|
||||
color: var(--inputtextcolor, #000);
|
||||
border: 1px solid var(--colorboxbordertitle1, #ccc);
|
||||
background: var(--colorbackbody, #2a2a30);
|
||||
color: var(--colortext, #ddd);
|
||||
border: 1px solid var(--colorboxbordertitle1, #555);
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.bericht-toolbar #zoom-label {
|
||||
color: var(--colortext, inherit);
|
||||
.bericht-toolbar select option {
|
||||
background: var(--colorbackbody, #2a2a30);
|
||||
color: var(--colortext, #ddd);
|
||||
}
|
||||
.bericht-toolbar #zoom-label { color: var(--colortext, inherit); }
|
||||
.bericht-toolbar input[type="checkbox"] { accent-color: var(--colorbackhmenu1, #337ab7); }
|
||||
|
||||
.bericht-canvas-wrap {
|
||||
position: relative;
|
||||
|
|
@ -139,34 +142,70 @@
|
|||
border: 1px solid var(--colorboxbordertitle1, #ccc);
|
||||
}
|
||||
|
||||
.page-list { display: flex; flex-direction: column; gap: 6px; }
|
||||
.page-thumb {
|
||||
background: var(--colorbacktitle1, #fff);
|
||||
.bericht-pages-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.bericht-pages-header h4 { margin: 0; }
|
||||
#btn-toggle-thumb-bg {
|
||||
background: transparent;
|
||||
border: 1px solid var(--colorboxbordertitle1, #555);
|
||||
color: var(--colortext, inherit);
|
||||
border: 2px solid var(--colorboxbordertitle1, #ddd);
|
||||
border-radius: 4px;
|
||||
padding: 6px; cursor: pointer; position: relative;
|
||||
transition: border-color 0.15s;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.page-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
.page-thumb {
|
||||
border: 2px solid var(--colorboxbordertitle1, #444);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.page-thumb:hover { border-color: var(--colortextlink, #888); }
|
||||
.page-thumb.active {
|
||||
border-color: var(--colortextlink, #337ab7);
|
||||
box-shadow: 0 0 0 2px var(--colorbackhmenu1, rgba(51,122,183,0.3));
|
||||
box-shadow: 0 0 0 2px rgba(51,122,183,0.4);
|
||||
}
|
||||
.page-thumb-inner {
|
||||
height: 100px;
|
||||
background: var(--colorbackvmenu1, #f0f0f0);
|
||||
|
||||
/* "Papier"-Look — heller Default, mit Papier-Schatten */
|
||||
.page-list.paper-light .page-thumb-paper { background: #ffffff; }
|
||||
.page-list.paper-dark .page-thumb-paper { background: #1e1e22; }
|
||||
|
||||
.page-thumb-paper {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1.414; /* DIN A4 Verhältnis (Hochformat) */
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 24px; opacity: 0.6;
|
||||
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.page-thumb-paper canvas.thumb-canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-thumb-label {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
padding: 3px 0;
|
||||
background: var(--colorbacktitle1, #2a2a30);
|
||||
color: var(--colortext, #ddd);
|
||||
border-top: 1px solid var(--colorboxbordertitle1, #444);
|
||||
}
|
||||
|
||||
.page-thumb-actions { position: absolute; top: 4px; right: 4px; }
|
||||
.thumb-del {
|
||||
background: var(--colorbacktitle1, rgba(255,255,255,0.9));
|
||||
color: var(--colortext, inherit);
|
||||
border: 1px solid var(--colorboxbordertitle1, #ccc);
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.thumb-del:hover { background: rgba(217,83,79,0.9); }
|
||||
|
||||
.bericht-actions {
|
||||
margin-top: 16px;
|
||||
|
|
|
|||
69
js/editor.js
69
js/editor.js
|
|
@ -55,6 +55,9 @@
|
|||
const firstThumb = document.querySelector('#bericht-page-list .page-thumb');
|
||||
if (firstThumb) loadPage(firstThumb);
|
||||
|
||||
// Alle Thumbnails parallel rendern
|
||||
renderAllThumbs();
|
||||
|
||||
bindThumbs();
|
||||
bindToolbar();
|
||||
bindAttachments();
|
||||
|
|
@ -590,6 +593,72 @@
|
|||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
// Hell/Dunkel-Toggle
|
||||
const tg = document.getElementById('btn-toggle-thumb-bg');
|
||||
if (tg) tg.addEventListener('click', () => {
|
||||
const list = document.getElementById('bericht-page-list');
|
||||
list.classList.toggle('paper-light');
|
||||
list.classList.toggle('paper-dark');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert alle Thumbnails in der rechten Seitenleiste.
|
||||
* Holt jedes Bild über page_image.php und zeichnet es klein in das Thumb-Canvas.
|
||||
* PDFs werden mit PDF.js gerendert.
|
||||
*/
|
||||
async function renderAllThumbs() {
|
||||
const thumbs = document.querySelectorAll('.page-thumb');
|
||||
for (const t of thumbs) {
|
||||
const pageid = t.dataset.pageid;
|
||||
const canvas = t.querySelector('.thumb-canvas');
|
||||
if (!canvas || !pageid) continue;
|
||||
try {
|
||||
const r = await fetch(cfg.urls.page_image + '?pageid=' + pageid);
|
||||
const ct = r.headers.get('Content-Type') || '';
|
||||
const buf = await r.arrayBuffer();
|
||||
if (ct.includes('pdf')) {
|
||||
await renderThumbPdf(canvas, buf);
|
||||
} else if (ct.includes('image')) {
|
||||
await renderThumbImage(canvas, buf, ct);
|
||||
}
|
||||
} catch (e) { /* skip */ }
|
||||
}
|
||||
}
|
||||
|
||||
async function renderThumbImage(canvas, buf, mime) {
|
||||
return new Promise(res => {
|
||||
const blob = new Blob([buf], { type: mime });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const maxSide = 200;
|
||||
const ratio = Math.min(maxSide / img.width, maxSide / img.height);
|
||||
canvas.width = Math.round(img.width * ratio);
|
||||
canvas.height = Math.round(img.height * ratio);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
URL.revokeObjectURL(url);
|
||||
res();
|
||||
};
|
||||
img.onerror = () => { URL.revokeObjectURL(url); res(); };
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
async function renderThumbPdf(canvas, buf) {
|
||||
if (!window.pdfjsLib) return;
|
||||
try {
|
||||
const doc = await pdfjsLib.getDocument({ data: buf.slice(0) }).promise;
|
||||
const page = await doc.getPage(1);
|
||||
const base = page.getViewport({ scale: 1 });
|
||||
const scale = 200 / base.width;
|
||||
const vp = page.getViewport({ scale: scale });
|
||||
canvas.width = vp.width;
|
||||
canvas.height = vp.height;
|
||||
await page.render({ canvasContext: canvas.getContext('2d'), viewport: vp }).promise;
|
||||
} catch (e) { /* skip */ }
|
||||
}
|
||||
|
||||
function bindSortable() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue