fix: resolve file viewer auth issues and add text file viewing support
- Fix file viewer not sending JWT auth tokens when loading files
- inlineViewer.js: already used XHR with auth (images/PDFs worked)
- fileViewer.js: was setting img.src/iframe.src directly without auth headers,
now uses fetch with Bearer token and blob URLs
- ui.js/contextMenus.js/fileRenderer.js/recent.js/favorites.js: replaced all
window.location.href = /api/files/... (unauthenticated navigation) with
authenticated viewer or fileOps.downloadFile()
- Add text file viewing support (text/*, application/json, etc.)
- New createTextViewer() in inlineViewer.js with authenticated fetch
- New loadTextViewer() in fileViewer.js with authenticated fetch
- New isViewableFile() helper in ui.js used across all entry points
- CSS styles for .inline-viewer-text-content and .file-viewer-text-content
- Translate remaining Spanish strings to English in viewer files
Fixes: text files showing 'Token not provided', images failing to load,
and text files not being previewable at all.
This commit is contained in:
@@ -123,6 +123,25 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text viewer styles */
|
||||||
|
.file-viewer-text-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-family: 'Courier New', Consolas, Monaco, monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #2d3748;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: left;
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Loader */
|
/* Loader */
|
||||||
.file-viewer-loader {
|
.file-viewer-loader {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -193,6 +193,25 @@
|
|||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text viewer */
|
||||||
|
.inline-viewer-text-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-family: 'Courier New', Consolas, Monaco, monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #2d3748;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: left;
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* Responsive adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.inline-viewer-content {
|
.inline-viewer-content {
|
||||||
|
|||||||
@@ -88,9 +88,8 @@ const contextMenus = {
|
|||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(fileDetails => {
|
.then(fileDetails => {
|
||||||
// Check if viewable file type
|
// Check if viewable file type (images, PDFs, text files)
|
||||||
if ((fileDetails.mime_type && fileDetails.mime_type.startsWith('image/')) ||
|
if (window.ui && window.ui.isViewableFile(fileDetails)) {
|
||||||
(fileDetails.mime_type && fileDetails.mime_type === 'application/pdf')) {
|
|
||||||
// Open with inline viewer
|
// Open with inline viewer
|
||||||
if (window.inlineViewer) {
|
if (window.inlineViewer) {
|
||||||
window.inlineViewer.openFile(fileDetails);
|
window.inlineViewer.openFile(fileDetails);
|
||||||
|
|||||||
+12
-4
@@ -609,9 +609,13 @@ const favorites = {
|
|||||||
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
|
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Download on click
|
// View or download on click
|
||||||
fileGridElement.addEventListener('click', () => {
|
fileGridElement.addEventListener('click', () => {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
if (window.ui && window.ui.isViewableFile(file) && window.inlineViewer) {
|
||||||
|
window.inlineViewer.openFile(file);
|
||||||
|
} else if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
@@ -654,9 +658,13 @@ const favorites = {
|
|||||||
<div class="date-cell">${formattedDate}</div>
|
<div class="date-cell">${formattedDate}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Download on click
|
// View or download on click
|
||||||
fileListElement.addEventListener('click', () => {
|
fileListElement.addEventListener('click', () => {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
if (window.ui && window.ui.isViewableFile(file) && window.inlineViewer) {
|
||||||
|
window.inlineViewer.openFile(file);
|
||||||
|
} else if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
|
|||||||
@@ -304,8 +304,10 @@ class FileRenderer {
|
|||||||
window.inlineViewer.openFile(item);
|
window.inlineViewer.openFile(item);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Inline viewer not available, downloading directly');
|
console.warn('Inline viewer not available, downloading directly');
|
||||||
// Fallback to direct download if viewer is not available
|
// Fallback to authenticated download if viewer is not available
|
||||||
window.location.href = `/api/files/${item.id}`;
|
if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(item.id, item.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -448,8 +450,10 @@ class FileRenderer {
|
|||||||
window.inlineViewer.openFile(item);
|
window.inlineViewer.openFile(item);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Inline viewer not available, downloading directly');
|
console.warn('Inline viewer not available, downloading directly');
|
||||||
// Fallback to direct download if viewer is not available
|
// Fallback to authenticated download if viewer is not available
|
||||||
window.location.href = `/api/files/${item.id}`;
|
if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(item.id, item.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-35
@@ -128,6 +128,9 @@ class FileViewer {
|
|||||||
} else if (fileData.mime_type && fileData.mime_type === 'application/pdf') {
|
} else if (fileData.mime_type && fileData.mime_type === 'application/pdf') {
|
||||||
console.log('FileViewer: Loading PDF viewer');
|
console.log('FileViewer: Loading PDF viewer');
|
||||||
this.loadPdfViewer(fileData.id, viewerArea);
|
this.loadPdfViewer(fileData.id, viewerArea);
|
||||||
|
} else if (fileData.mime_type && this.isTextViewable(fileData.mime_type)) {
|
||||||
|
console.log('FileViewer: Loading text viewer');
|
||||||
|
this.loadTextViewer(fileData.id, viewerArea);
|
||||||
} else {
|
} else {
|
||||||
console.log('FileViewer: Unsupported file type', fileData.mime_type);
|
console.log('FileViewer: Unsupported file type', fileData.mime_type);
|
||||||
// For unsupported files, show download prompt
|
// For unsupported files, show download prompt
|
||||||
@@ -141,25 +144,37 @@ class FileViewer {
|
|||||||
* @param {HTMLElement} container - Container element to render into
|
* @param {HTMLElement} container - Container element to render into
|
||||||
*/
|
*/
|
||||||
loadImageViewer(fileId, container) {
|
loadImageViewer(fileId, container) {
|
||||||
// Create image element
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.className = 'file-viewer-image';
|
|
||||||
img.src = `/api/files/${fileId}`;
|
|
||||||
img.alt = this.fileData.name;
|
|
||||||
|
|
||||||
// Create loader
|
// Create loader
|
||||||
const loader = document.createElement('div');
|
const loader = document.createElement('div');
|
||||||
loader.className = 'file-viewer-loader';
|
loader.className = 'file-viewer-loader';
|
||||||
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
container.appendChild(loader);
|
container.appendChild(loader);
|
||||||
|
|
||||||
// When image loads, remove loader
|
// Fetch image with auth header and create blob URL
|
||||||
img.onload = () => {
|
this.fetchFileAsBlob(fileId).then(blob => {
|
||||||
container.removeChild(loader);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
};
|
this.currentBlobUrl = blobUrl;
|
||||||
|
|
||||||
// Add image to container
|
const img = document.createElement('img');
|
||||||
container.appendChild(img);
|
img.className = 'file-viewer-image';
|
||||||
|
img.src = blobUrl;
|
||||||
|
img.alt = this.fileData.name;
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
this.showErrorMessage(container);
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(img);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error loading image:', error);
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
this.showErrorMessage(container);
|
||||||
|
});
|
||||||
|
|
||||||
// Add zoom controls to toolbar
|
// Add zoom controls to toolbar
|
||||||
const toolbar = this.viewerContainer.querySelector('.file-viewer-toolbar');
|
const toolbar = this.viewerContainer.querySelector('.file-viewer-toolbar');
|
||||||
@@ -220,25 +235,87 @@ class FileViewer {
|
|||||||
* @param {HTMLElement} container - Container element to render into
|
* @param {HTMLElement} container - Container element to render into
|
||||||
*/
|
*/
|
||||||
loadPdfViewer(fileId, container) {
|
loadPdfViewer(fileId, container) {
|
||||||
// Create iframe for PDF viewer
|
|
||||||
const iframe = document.createElement('iframe');
|
|
||||||
iframe.className = 'file-viewer-pdf';
|
|
||||||
iframe.src = `/api/files/${fileId}`;
|
|
||||||
iframe.title = this.fileData.name;
|
|
||||||
|
|
||||||
// Create loader
|
// Create loader
|
||||||
const loader = document.createElement('div');
|
const loader = document.createElement('div');
|
||||||
loader.className = 'file-viewer-loader';
|
loader.className = 'file-viewer-loader';
|
||||||
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
container.appendChild(loader);
|
container.appendChild(loader);
|
||||||
|
|
||||||
// When iframe loads, remove loader
|
// Fetch PDF with auth header and create blob URL
|
||||||
iframe.onload = () => {
|
this.fetchFileAsBlob(fileId).then(blob => {
|
||||||
container.removeChild(loader);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
};
|
this.currentBlobUrl = blobUrl;
|
||||||
|
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.className = 'file-viewer-pdf';
|
||||||
|
iframe.src = blobUrl;
|
||||||
|
iframe.title = this.fileData.name;
|
||||||
|
|
||||||
|
iframe.onload = () => {
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(iframe);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error loading PDF:', error);
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
this.showErrorMessage(container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the text viewer
|
||||||
|
*/
|
||||||
|
async loadTextViewer(fileId, container) {
|
||||||
|
const loader = document.createElement('div');
|
||||||
|
loader.className = 'file-viewer-loader';
|
||||||
|
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
|
container.appendChild(loader);
|
||||||
|
|
||||||
// Add iframe to container
|
try {
|
||||||
container.appendChild(iframe);
|
const token = localStorage.getItem('oxicloud_token');
|
||||||
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||||
|
const response = await fetch(`/api/files/${fileId}?inline=true`, { headers });
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
pre.className = 'file-viewer-text-content';
|
||||||
|
pre.textContent = text;
|
||||||
|
container.appendChild(pre);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading text:', error);
|
||||||
|
if (loader.parentNode) container.removeChild(loader);
|
||||||
|
this.showErrorMessage(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a MIME type is text-viewable
|
||||||
|
*/
|
||||||
|
isTextViewable(mimeType) {
|
||||||
|
if (!mimeType) return false;
|
||||||
|
if (mimeType.startsWith('text/')) return true;
|
||||||
|
const textTypes = [
|
||||||
|
'application/json', 'application/xml', 'application/javascript',
|
||||||
|
'application/x-sh', 'application/x-yaml', 'application/toml',
|
||||||
|
'application/x-toml', 'application/sql',
|
||||||
|
];
|
||||||
|
return textTypes.includes(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a file as blob with auth headers
|
||||||
|
*/
|
||||||
|
async fetchFileAsBlob(fileId) {
|
||||||
|
const token = localStorage.getItem('oxicloud_token');
|
||||||
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||||
|
const response = await fetch(`/api/files/${fileId}?inline=true`, { headers });
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
return response.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -251,10 +328,10 @@ class FileViewer {
|
|||||||
|
|
||||||
message.innerHTML = `
|
message.innerHTML = `
|
||||||
<i class="fas fa-file-download"></i>
|
<i class="fas fa-file-download"></i>
|
||||||
<p>${window.i18n ? window.i18n.t('viewer.unsupported_file') : 'Este tipo de archivo no se puede previsualizar.'}</p>
|
<p>${window.i18n ? window.i18n.t('viewer.unsupported_file') : 'This file type cannot be previewed.'}</p>
|
||||||
<button class="btn btn-primary download-btn">
|
<button class="btn btn-primary download-btn">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
${window.i18n ? window.i18n.t('viewer.download_file') : 'Descargar archivo'}
|
${window.i18n ? window.i18n.t('viewer.download_file') : 'Download file'}
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -272,14 +349,39 @@ class FileViewer {
|
|||||||
downloadFile() {
|
downloadFile() {
|
||||||
if (!this.fileData) return;
|
if (!this.fileData) return;
|
||||||
|
|
||||||
// Create a link and simulate click
|
// Download with auth headers
|
||||||
const link = document.createElement('a');
|
const token = localStorage.getItem('oxicloud_token');
|
||||||
link.href = `/api/files/${this.fileData.id}`;
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||||
link.download = this.fileData.name;
|
|
||||||
link.target = '_blank';
|
fetch(`/api/files/${this.fileData.id}`, { headers })
|
||||||
document.body.appendChild(link);
|
.then(res => {
|
||||||
link.click();
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
document.body.removeChild(link);
|
return res.blob();
|
||||||
|
})
|
||||||
|
.then(blob => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = this.fileData.name;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Download error:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message
|
||||||
|
*/
|
||||||
|
showErrorMessage(container) {
|
||||||
|
const message = document.createElement('div');
|
||||||
|
message.className = 'file-viewer-unsupported';
|
||||||
|
message.innerHTML = `
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<p>Error loading the file. Try downloading it directly.</p>
|
||||||
|
`;
|
||||||
|
container.appendChild(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,6 +392,12 @@ class FileViewer {
|
|||||||
this.fileData = null;
|
this.fileData = null;
|
||||||
this.viewerContainer.classList.remove('active');
|
this.viewerContainer.classList.remove('active');
|
||||||
|
|
||||||
|
// Clean up blob URL if exists
|
||||||
|
if (this.currentBlobUrl) {
|
||||||
|
URL.revokeObjectURL(this.currentBlobUrl);
|
||||||
|
this.currentBlobUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset toolbar (remove zoom controls)
|
// Reset toolbar (remove zoom controls)
|
||||||
const toolbar = this.viewerContainer.querySelector('.file-viewer-toolbar');
|
const toolbar = this.viewerContainer.querySelector('.file-viewer-toolbar');
|
||||||
const downloadBtn = toolbar.querySelector('.file-viewer-download');
|
const downloadBtn = toolbar.querySelector('.file-viewer-download');
|
||||||
|
|||||||
@@ -133,6 +133,19 @@ class InlineViewer {
|
|||||||
// Create PDF viewer using object tag with blob URL
|
// Create PDF viewer using object tag with blob URL
|
||||||
this.createBlobUrlViewer(file, 'pdf', container, loader);
|
this.createBlobUrlViewer(file, 'pdf', container, loader);
|
||||||
}
|
}
|
||||||
|
else if (file.mime_type && this.isTextViewable(file.mime_type)) {
|
||||||
|
// Hide zoom controls for text files
|
||||||
|
controls.style.display = 'none';
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
const loader = document.createElement('div');
|
||||||
|
loader.className = 'inline-viewer-loader';
|
||||||
|
loader.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
|
container.appendChild(loader);
|
||||||
|
|
||||||
|
// Create text viewer using authenticated fetch
|
||||||
|
this.createTextViewer(file, container, loader);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// Hide zoom controls for unsupported files
|
// Hide zoom controls for unsupported files
|
||||||
controls.style.display = 'none';
|
controls.style.display = 'none';
|
||||||
@@ -143,8 +156,8 @@ class InlineViewer {
|
|||||||
message.innerHTML = `
|
message.innerHTML = `
|
||||||
<div class="inline-viewer-icon"><i class="fas fa-file"></i></div>
|
<div class="inline-viewer-icon"><i class="fas fa-file"></i></div>
|
||||||
<div class="inline-viewer-text">
|
<div class="inline-viewer-text">
|
||||||
<p>Este tipo de archivo no puede ser previsualizado.</p>
|
<p>This file type cannot be previewed.</p>
|
||||||
<p>Haz clic en "Descargar" para obtener el archivo.</p>
|
<p>Click "Download" to get the file.</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(message);
|
container.appendChild(message);
|
||||||
@@ -154,6 +167,63 @@ class InlineViewer {
|
|||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a MIME type is text-viewable
|
||||||
|
isTextViewable(mimeType) {
|
||||||
|
if (!mimeType) return false;
|
||||||
|
if (mimeType.startsWith('text/')) return true;
|
||||||
|
const textTypes = [
|
||||||
|
'application/json',
|
||||||
|
'application/xml',
|
||||||
|
'application/javascript',
|
||||||
|
'application/x-sh',
|
||||||
|
'application/x-yaml',
|
||||||
|
'application/toml',
|
||||||
|
'application/x-toml',
|
||||||
|
'application/sql',
|
||||||
|
];
|
||||||
|
return textTypes.includes(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a text viewer using authenticated fetch
|
||||||
|
async createTextViewer(file, container, loader) {
|
||||||
|
try {
|
||||||
|
console.log('Creating text viewer for:', file.name);
|
||||||
|
|
||||||
|
const token = localStorage.getItem('oxicloud_token');
|
||||||
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||||
|
|
||||||
|
const response = await fetch(`/api/files/${file.id}?inline=true`, { headers });
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error fetching file: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
// Remove loader
|
||||||
|
if (loader && loader.parentNode) {
|
||||||
|
loader.parentNode.removeChild(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create text viewer element
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
pre.className = 'inline-viewer-text-content';
|
||||||
|
pre.textContent = text;
|
||||||
|
container.appendChild(pre);
|
||||||
|
|
||||||
|
console.log('Text viewer created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating text viewer:', error);
|
||||||
|
|
||||||
|
// Remove loader
|
||||||
|
if (loader && loader.parentNode) {
|
||||||
|
loader.parentNode.removeChild(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showErrorMessage(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Creates a viewer using a Blob URL to avoid content-disposition header
|
// Creates a viewer using a Blob URL to avoid content-disposition header
|
||||||
async createBlobUrlViewer(file, type, container, loader) {
|
async createBlobUrlViewer(file, type, container, loader) {
|
||||||
try {
|
try {
|
||||||
@@ -269,8 +339,8 @@ class InlineViewer {
|
|||||||
message.innerHTML = `
|
message.innerHTML = `
|
||||||
<div class="inline-viewer-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
<div class="inline-viewer-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
<div class="inline-viewer-text">
|
<div class="inline-viewer-text">
|
||||||
<p>Error al cargar el archivo.</p>
|
<p>Error loading the file.</p>
|
||||||
<p>Intenta descargarlo directamente.</p>
|
<p>Try downloading it directly.</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(message);
|
container.appendChild(message);
|
||||||
|
|||||||
+12
-4
@@ -207,9 +207,13 @@ const recent = {
|
|||||||
<div class="file-info">Accessed ${formattedDate.split(' ')[0]}</div>
|
<div class="file-info">Accessed ${formattedDate.split(' ')[0]}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Download on click
|
// View or download on click
|
||||||
fileGridElement.addEventListener('click', () => {
|
fileGridElement.addEventListener('click', () => {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
if (window.ui && window.ui.isViewableFile(file) && window.inlineViewer) {
|
||||||
|
window.inlineViewer.openFile(file);
|
||||||
|
} else if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch custom event to update recent files
|
// Dispatch custom event to update recent files
|
||||||
document.dispatchEvent(new CustomEvent('file-accessed', {
|
document.dispatchEvent(new CustomEvent('file-accessed', {
|
||||||
@@ -257,9 +261,13 @@ const recent = {
|
|||||||
<div class="date-cell">${formattedDate}</div>
|
<div class="date-cell">${formattedDate}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Download on click
|
// View or download on click
|
||||||
fileListElement.addEventListener('click', () => {
|
fileListElement.addEventListener('click', () => {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
if (window.ui && window.ui.isViewableFile(file) && window.inlineViewer) {
|
||||||
|
window.inlineViewer.openFile(file);
|
||||||
|
} else if (window.fileOps) {
|
||||||
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch custom event to update recent files
|
// Dispatch custom event to update recent files
|
||||||
document.dispatchEvent(new CustomEvent('file-accessed', {
|
document.dispatchEvent(new CustomEvent('file-accessed', {
|
||||||
|
|||||||
+24
-8
@@ -434,6 +434,24 @@ const ui = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file can be previewed in the viewer
|
||||||
|
* @param {Object} file - File object with mime_type property
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isViewableFile(file) {
|
||||||
|
if (!file || !file.mime_type) return false;
|
||||||
|
if (file.mime_type.startsWith('image/')) return true;
|
||||||
|
if (file.mime_type === 'application/pdf') return true;
|
||||||
|
if (file.mime_type.startsWith('text/')) return true;
|
||||||
|
const textTypes = [
|
||||||
|
'application/json', 'application/xml', 'application/javascript',
|
||||||
|
'application/x-sh', 'application/x-yaml', 'application/toml',
|
||||||
|
'application/x-toml', 'application/sql',
|
||||||
|
];
|
||||||
|
return textTypes.includes(file.mime_type);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show notification
|
* Show notification
|
||||||
* @param {string} title - Notification title
|
* @param {string} title - Notification title
|
||||||
@@ -957,17 +975,16 @@ const ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a viewable file type
|
// Check if it's a viewable file type
|
||||||
if ((file.mime_type && file.mime_type.startsWith('image/')) ||
|
if (this.isViewableFile(file)) {
|
||||||
(file.mime_type && file.mime_type === 'application/pdf')) {
|
|
||||||
if (window.inlineViewer) {
|
if (window.inlineViewer) {
|
||||||
window.inlineViewer.openFile(file);
|
window.inlineViewer.openFile(file);
|
||||||
} else if (window.fileViewer) {
|
} else if (window.fileViewer) {
|
||||||
window.fileViewer.open(file);
|
window.fileViewer.open(file);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.location.href = `/api/files/${file.id}`;
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1051,8 +1068,7 @@ const ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a viewable file type
|
// Check if it's a viewable file type
|
||||||
if ((file.mime_type && file.mime_type.startsWith('image/')) ||
|
if (this.isViewableFile(file)) {
|
||||||
(file.mime_type && file.mime_type === 'application/pdf')) {
|
|
||||||
// Open in the inline viewer
|
// Open in the inline viewer
|
||||||
if (window.inlineViewer) {
|
if (window.inlineViewer) {
|
||||||
window.inlineViewer.openFile(file);
|
window.inlineViewer.openFile(file);
|
||||||
@@ -1061,11 +1077,11 @@ const ui = {
|
|||||||
window.fileViewer.open(file);
|
window.fileViewer.open(file);
|
||||||
} else {
|
} else {
|
||||||
// No viewer available, download directly
|
// No viewer available, download directly
|
||||||
window.location.href = `/api/files/${file.id}`;
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For other file types, download directly
|
// For other file types, download directly
|
||||||
window.location.href = `/api/files/${file.id}`;
|
window.fileOps.downloadFile(file.id, file.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user