diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ed07af..bf029bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# CHANGELOG
+## v4.0.2 – 2026-07-07
+
+- Add fullscreen mode (hides navbar and expands viewer to full viewport) and a new share-link option to hide the toolbar, ideal for student-facing links.
+
+---
+
## v4.0.1 – 2026-06-09
- Fix Dropbox, Nextcloud and ownCloud shared-link downloads by routing them through `github-proxy.exelearning.dev`.
diff --git a/css/styles.css b/css/styles.css
index 9415b75..816a7bc 100644
--- a/css/styles.css
+++ b/css/styles.css
@@ -305,6 +305,12 @@ body {
background-color: var(--surface-color);
}
+/* Fullscreen mode: hide navbar, expand viewer to full viewport */
+body.fullscreen-mode .viewer-container {
+ height: 100vh;
+ margin-top: 0;
+}
+
.content-frame {
width: 100%;
height: 100%;
diff --git a/index.html b/index.html
index aa1c3a5..fa0d685 100644
--- a/index.html
+++ b/index.html
@@ -236,6 +236,15 @@
Share
Users accessing the link will see a button to download the original file.
+
+
+
+
+
+ Hides the toolbar. Ideal for sending to students.
+
Link copied to clipboard!
diff --git a/js/app.js b/js/app.js
index 0032e36..36b60f9 100644
--- a/js/app.js
+++ b/js/app.js
@@ -9,7 +9,7 @@
// Configuration defaults — override via js/config.js (window.exeViewerConfig)
const config = Object.assign({
// Application version (displayed in footer)
- version: '4.0.1',
+ version: '4.0.2',
// Automatically restore and display content from IndexedDB on page load
autoRestoreContent: true,
// Open external links in a new window/tab (prevents navigation issues in iframes)
@@ -37,7 +37,8 @@
zipWorker: null, // Web Worker for ZIP extraction
isRestoredContent: false, // True if content was restored from IndexedDB
currentErrorKey: null, // Current error translation key (for language updates)
- currentStorageWarningKey: null // Current storage warning key (for language updates)
+ currentStorageWarningKey: null, // Current storage warning key (for language updates)
+ fullscreenMode: false // True when ?fullscreen=1 is present in the URL
};
// DOM Elements
@@ -69,6 +70,7 @@
copySuccess: null,
linkUpdated: null,
allowDownloadCheck: null,
+ fullscreenCheck: null,
// Footer element
footerInfo: null,
// URL label element
@@ -179,6 +181,7 @@
elements.copySuccess = document.getElementById('copySuccess');
elements.linkUpdated = document.getElementById('linkUpdated');
elements.allowDownloadCheck = document.getElementById('allowDownloadCheck');
+ elements.fullscreenCheck = document.getElementById('fullscreenCheck');
// Footer element
elements.footerInfo = document.getElementById('footerInfo');
// URL label element
@@ -1126,25 +1129,31 @@
// Update UI
elements.welcomeScreen.classList.add('d-none');
elements.viewerContainer.classList.remove('d-none');
- elements.topNavbar.classList.remove('d-none');
- // Update package name in navbar
- if (state.currentPackageName) {
- elements.packageName.textContent = state.currentPackageName;
- elements.packageName.title = state.currentPackageName;
- // Show package name visually only for restored content
- if (state.isRestoredContent) {
- elements.packageName.classList.remove('visually-hidden');
+ if (state.fullscreenMode) {
+ // Keep navbar hidden; viewer fills full viewport via CSS
+ elements.topNavbar.classList.add('d-none');
+ } else {
+ elements.topNavbar.classList.remove('d-none');
+
+ // Update package name in navbar
+ if (state.currentPackageName) {
+ elements.packageName.textContent = state.currentPackageName;
+ elements.packageName.title = state.currentPackageName;
+ // Show package name visually only for restored content
+ if (state.isRestoredContent) {
+ elements.packageName.classList.remove('visually-hidden');
+ }
}
- }
- // Update share and download button visibility
- updateShareButtonVisibility();
- updateDownloadButtonVisibility();
+ // Update share and download button visibility
+ updateShareButtonVisibility();
+ updateDownloadButtonVisibility();
- // Show open in new window and exit buttons
- elements.btnNewWindow.classList.remove('d-none');
- elements.btnLoadNew.classList.remove('d-none');
+ // Show open in new window and exit buttons
+ elements.btnNewWindow.classList.remove('d-none');
+ elements.btnLoadNew.classList.remove('d-none');
+ }
// Set up history states: first mark current state as welcome, then push viewer state
const welcomeState = { isWelcome: true };
@@ -1479,15 +1488,19 @@
/**
* Generate the share URL for the current content
* @param {boolean} allowDownload - Whether to include the download parameter
+ * @param {boolean} fullscreen - Whether to hide the toolbar (fullscreen mode)
* @returns {string} The share URL
*/
- function generateShareUrl(allowDownload = false) {
+ function generateShareUrl(allowDownload = false, fullscreen = false) {
const baseUrl = window.location.origin + window.location.pathname;
const resourceUrl = state.contentFromUrl;
let url = `${baseUrl}?url=${encodeURIComponent(resourceUrl)}`;
if (allowDownload) {
url += '&download=1';
}
+ if (fullscreen) {
+ url += '&fullscreen=1';
+ }
return url;
}
@@ -1555,35 +1568,44 @@
* Open the share modal with the generated URL
*/
function openShareModal() {
- // Initialize checkbox state based on config
+ // Initialize checkbox states
elements.allowDownloadCheck.checked = config.allowDownloadByDefault;
+ elements.fullscreenCheck.checked = false;
- // Generate URL with current checkbox state
- const shareUrl = generateShareUrl(elements.allowDownloadCheck.checked);
+ // Generate URL with current checkbox states
+ const shareUrl = generateShareUrl(elements.allowDownloadCheck.checked, elements.fullscreenCheck.checked);
elements.shareUrlInput.value = shareUrl;
// Hide feedback messages
elements.copySuccess.classList.add('d-none');
elements.linkUpdated.classList.add('d-none');
- // Remove any previous event listener to avoid duplicates
- const newCheckbox = elements.allowDownloadCheck.cloneNode(true);
- elements.allowDownloadCheck.parentNode.replaceChild(newCheckbox, elements.allowDownloadCheck);
- elements.allowDownloadCheck = newCheckbox;
+ // Remove any previous event listeners to avoid duplicates
+ const newDownloadCheck = elements.allowDownloadCheck.cloneNode(true);
+ elements.allowDownloadCheck.parentNode.replaceChild(newDownloadCheck, elements.allowDownloadCheck);
+ elements.allowDownloadCheck = newDownloadCheck;
- // Add event listener for checkbox changes
- elements.allowDownloadCheck.addEventListener('change', function() {
- const newUrl = generateShareUrl(this.checked);
- elements.shareUrlInput.value = newUrl;
+ const newFullscreenCheck = elements.fullscreenCheck.cloneNode(true);
+ elements.fullscreenCheck.parentNode.replaceChild(newFullscreenCheck, elements.fullscreenCheck);
+ elements.fullscreenCheck = newFullscreenCheck;
- // Show "Link updated" feedback
+ function updateShareUrl() {
+ const newUrl = generateShareUrl(elements.allowDownloadCheck.checked, elements.fullscreenCheck.checked);
+ elements.shareUrlInput.value = newUrl;
elements.copySuccess.classList.add('d-none');
elements.linkUpdated.classList.remove('d-none');
-
- // Hide feedback after 2 seconds
setTimeout(() => {
elements.linkUpdated.classList.add('d-none');
}, 2000);
+ }
+
+ elements.allowDownloadCheck.addEventListener('change', () => {
+ if (elements.allowDownloadCheck.checked) elements.fullscreenCheck.checked = false;
+ updateShareUrl();
+ });
+ elements.fullscreenCheck.addEventListener('change', () => {
+ if (elements.fullscreenCheck.checked) elements.allowDownloadCheck.checked = false;
+ updateShareUrl();
});
const modal = new bootstrap.Modal(elements.shareModal);
@@ -1686,11 +1708,13 @@
if (elements.viewerContainer.classList.contains('d-none')) {
elements.welcomeScreen.classList.add('d-none');
elements.viewerContainer.classList.remove('d-none');
- elements.topNavbar.classList.remove('d-none');
- elements.btnNewWindow.classList.remove('d-none');
- elements.btnLoadNew.classList.remove('d-none');
- updateShareButtonVisibility();
- updateDownloadButtonVisibility();
+ if (!state.fullscreenMode) {
+ elements.topNavbar.classList.remove('d-none');
+ elements.btnNewWindow.classList.remove('d-none');
+ elements.btnLoadNew.classList.remove('d-none');
+ updateShareButtonVisibility();
+ updateDownloadButtonVisibility();
+ }
}
// Increment counter to ignore the upcoming iframe load event
state.historyNavigationCount++;
@@ -1788,6 +1812,13 @@
// Check for URL parameter and auto-load if present
const urlParams = new URLSearchParams(window.location.search);
+
+ // Detect fullscreen mode (?fullscreen=1) and apply body class immediately
+ if (urlParams.get('fullscreen') === '1') {
+ state.fullscreenMode = true;
+ document.body.classList.add('fullscreen-mode');
+ }
+
if (urlParams.get('url')) {
await checkUrlParameter();
} else if (config.autoRestoreContent) {
diff --git a/lang/en.json b/lang/en.json
index 859ea51..2975c48 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -79,7 +79,9 @@
"copied": "Link copied to clipboard!",
"linkUpdated": "Link updated!",
"allowDownload": "Download button",
- "allowDownloadDesc": "Users accessing the link will see a button to download the original file."
+ "allowDownloadDesc": "Users accessing the link will see a button to download the original file.",
+ "fullscreen": "Content only",
+ "fullscreenDesc": "Hides the toolbar. Ideal for sending to students."
},
"download": {
"tooltip": "Download original file"
diff --git a/lang/es.json b/lang/es.json
index 26b586e..a267a20 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -79,7 +79,9 @@
"copied": "Enlace copiado al portapapeles.",
"linkUpdated": "Enlace actualizado.",
"allowDownload": "Botón de descarga",
- "allowDownloadDesc": "Los usuarios que accedan al enlace verán un botón para descargar el fichero original."
+ "allowDownloadDesc": "Los usuarios que accedan al enlace verán un botón para descargar el fichero original.",
+ "fullscreen": "Solo contenido",
+ "fullscreenDesc": "Oculta la barra superior. Ideal para enviar a alumnos."
},
"download": {
"tooltip": "Descargar fichero original"
diff --git a/package-lock.json b/package-lock.json
index 655f6ce..f827a87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "exeviewer",
- "version": "4.0.1",
+ "version": "4.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/package.json b/package.json
index e6cbfe4..f314a18 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "exeviewer",
- "version": "4.0.1",
+ "version": "4.0.2",
"description": "A web application to view eXeLearning content packages (.zip/.elpx)",
"main": "server.js",
"type": "module",