Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
6 changes: 6 additions & 0 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down
9 changes: 9 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ <h5 class="modal-title" id="shareModalLabel" data-i18n="share.modalTitle">Share
<p id="allowDownloadDesc" class="text-muted small mt-1 mb-0" data-i18n="share.allowDownloadDesc">
Users accessing the link will see a button to download the original file.
</p>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="fullscreenCheck" aria-describedby="fullscreenCheckDesc">
<label class="form-check-label" for="fullscreenCheck" data-i18n="share.fullscreen">
Content only
</label>
</div>
<p id="fullscreenCheckDesc" class="text-muted small mt-1 mb-0" data-i18n="share.fullscreenDesc">
Hides the toolbar. Ideal for sending to students.
</p>
<div id="copySuccess" class="alert-success-text small mt-2 d-none" role="status" aria-live="polite" data-i18n="share.copied">
Link copied to clipboard!
</div>
Expand Down
105 changes: 68 additions & 37 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +70,7 @@
copySuccess: null,
linkUpdated: null,
allowDownloadCheck: null,
fullscreenCheck: null,
// Footer element
footerInfo: null,
// URL label element
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down