added presentation viewers
RSE-PSI Website Scheduler / checkout (pull_request) Successful in 32s

This commit is contained in:
Michael Weinold
2026-03-04 11:24:05 +01:00
parent c7af276158
commit 6852076bb2
15 changed files with 404 additions and 132 deletions
+66
View File
@@ -0,0 +1,66 @@
/**
* Navigation controller for Remark.js presentations in iframes.
*
* Looks for elements with class 'html-viewer-container'.
*/
document$.subscribe(({ body }) => {
const containers = body.querySelectorAll('.html-viewer-container');
containers.forEach(container => {
const iframe = container.querySelector('iframe');
const prevBtn = container.querySelector('.prev-btn');
const nextBtn = container.querySelector('.next-btn');
const pageNumDisplay = container.querySelector('.page-num');
const pageCountDisplay = container.querySelector('.page-count');
if (!iframe || !prevBtn || !nextBtn) return;
function updatePageInfo() {
try {
if (iframe.contentWindow && iframe.contentWindow.slideshow) {
const slideshow = iframe.contentWindow.slideshow;
const currentIndex = slideshow.getCurrentSlideIndex() + 1;
const totalSlides = slideshow.getSlides().length;
if (pageNumDisplay) pageNumDisplay.textContent = currentIndex;
if (pageCountDisplay) pageCountDisplay.textContent = totalSlides;
}
} catch (e) {
// Silently fail if cross-origin or slideshow not yet loaded
}
}
prevBtn.addEventListener('click', () => {
try {
if (iframe.contentWindow && iframe.contentWindow.slideshow) {
iframe.contentWindow.slideshow.gotoPreviousSlide();
updatePageInfo();
} else {
iframe.contentWindow.dispatchEvent(new KeyboardEvent('keydown', { 'keyCode': 37 }));
}
} catch (e) {
console.error("Could not navigate slideshow:", e);
}
});
nextBtn.addEventListener('click', () => {
try {
if (iframe.contentWindow && iframe.contentWindow.slideshow) {
iframe.contentWindow.slideshow.gotoNextSlide();
updatePageInfo();
} else {
iframe.contentWindow.dispatchEvent(new KeyboardEvent('keydown', { 'keyCode': 39 }));
}
} catch (e) {
console.error("Could not navigate slideshow:", e);
}
});
// Update info when iframe loads
iframe.addEventListener('load', updatePageInfo);
// Periodically update in case the user navigates using the keyboard inside the iframe
setInterval(updatePageInfo, 500);
});
});
+108
View File
@@ -0,0 +1,108 @@
/**
* Reusable PDF Viewer for MkDocs Material
*
* Uses PDF.js to render PDF files in a canvas.
* Looks for elements with class 'pdf-viewer-container' and a 'data-url' attribute.
*/
document$.subscribe(({ body }) => {
const containers = body.querySelectorAll('.pdf-viewer-container[data-url]');
if (containers.length === 0) return;
// Load PDF.js worker if not already loaded
if (window['pdfjs-dist/build/pdf'] && !window['pdfjs-dist/build/pdf'].GlobalWorkerOptions.workerSrc) {
window['pdfjs-dist/build/pdf'].GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
}
containers.forEach(container => {
initPDFViewer(container);
});
});
function initPDFViewer(container) {
const url = container.getAttribute('data-url');
const canvas = container.querySelector('canvas');
const pageNumDisplay = container.querySelector('.page-num');
const pageCountDisplay = container.querySelector('.page-count');
const prevBtn = container.querySelector('.prev-btn');
const nextBtn = container.querySelector('.next-btn');
const loadingMsg = container.querySelector('.pdf-viewer-loading');
if (!canvas || !url) return;
let pdfDoc = null;
let pageNum = 1;
let pageRendering = false;
let pageNumPending = null;
const scale = 2.0;
const ctx = canvas.getContext('2d');
function renderPage(num) {
pageRendering = true;
pdfDoc.getPage(num).then(page => {
if (loadingMsg) loadingMsg.style.display = 'none';
const viewport = page.getViewport({ scale: scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
const renderTask = page.render(renderContext);
renderTask.promise.then(() => {
pageRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
if (pageNumDisplay) pageNumDisplay.textContent = num;
}
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
if (prevBtn) {
prevBtn.addEventListener('click', () => {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
});
}
if (nextBtn) {
nextBtn.addEventListener('click', () => {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
});
}
// Load the document
const pdfjsLib = window['pdfjs-dist/build/pdf'];
if (!pdfjsLib) {
console.error('PDF.js library not found');
return;
}
pdfjsLib.getDocument(url).promise.then(pdfDoc_ => {
pdfDoc = pdfDoc_;
if (pageCountDisplay) pageCountDisplay.textContent = pdfDoc.numPages;
renderPage(pageNum);
}).catch(err => {
console.error('Error loading PDF:', err);
if (loadingMsg) loadingMsg.textContent = "Error loading PDF.";
});
}