Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 6 additions & 1 deletion src/interfaces/api/handlers/file_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,10 +811,15 @@ impl FileHandler {
/// filenames with quotes, non-ASCII characters, or other special chars.
/// A sanitised ASCII `filename=` fallback is included for legacy clients.
fn content_disposition(name: &str, mime: &str, params: &HashMap<String, String>) -> String {
let force_download = params
.get("download")
.is_some_and(|v| v == "true" || v == "1");
let force_inline = params
.get("inline")
.is_some_and(|v| v == "true" || v == "1");
let disposition = if force_inline
let disposition = if force_download {
"attachment"
} else if force_inline
|| mime.starts_with("image/")
|| mime == "application/pdf"
|| mime.starts_with("video/")
Expand Down
9 changes: 5 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// origin is configured at runtime (Collabora, OnlyOffice, etc.).
HeaderValue::from_static(
"default-src 'self'; \
script-src 'self'; \
style-src 'self'; \
script-src 'self' 'wasm-unsafe-eval'; \
style-src 'self' 'unsafe-inline'; \
img-src 'self' data: blob:; \
connect-src 'self'; \
media-src 'self' blob:; \
connect-src 'self' blob:; \
font-src 'self' data:; \
frame-src *; \
frame-ancestors 'none'; \
frame-ancestors 'self'; \
base-uri 'self'; \
form-action 'self'",
),
Expand Down
1 change: 0 additions & 1 deletion static/css/components/fileList.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* File list - Improved style */
.files-list-view {
--files-list-columns: 36px minmax(200px, 2fr) 100px 110px 160px;
display: flex;
flex-direction: column;
width: 100%;
border-radius: 10px;
Expand Down
9 changes: 9 additions & 0 deletions static/css/components/languageSelector.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@
background-color: #334155;
}

[data-theme="dark"] .language-selector-toggle .lang-code {
color: #cbd5e1;
}

[data-theme="dark"] .language-selector-toggle i,
[data-theme="dark"] .language-selector-toggle .dropdown-arrow {
color: #94a3b8;
}

[data-theme="dark"] .language-selector-dropdown {
background-color: #1e293b;
border-color: #334155;
Expand Down
102 changes: 102 additions & 0 deletions static/css/views/inlineViewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,108 @@
outline: none;
}

/* Toolbar right group */
.inline-viewer-toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}

/* Fullscreen toggle button */
button.inline-viewer-fullscreen {
background-color: #f1f5f9;
border: 1px solid #cbd5e1;
border-radius: 4px;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #64748b;
transition: all 0.2s;
}

button.inline-viewer-fullscreen:hover {
background-color: #e2e8f0;
color: #334155;
}

/* Fullscreen mode */
.inline-viewer-content.inline-viewer-fullscreen {
width: 100vw;
height: 100vh;
max-width: 100vw;
border-radius: 0;
transition: all 0.2s ease;
}

/* ── Dark mode ── */
[data-theme="dark"] .inline-viewer-content {
background-color: #1e293b;
}

[data-theme="dark"] .inline-viewer-header {
background-color: #2a3042;
border-bottom-color: #334155;
}

[data-theme="dark"] .inline-viewer-title {
color: #e2e8f0;
}

[data-theme="dark"] .inline-viewer-close {
color: #94a3b8;
}

[data-theme="dark"] .inline-viewer-close:hover {
background-color: #334155;
color: #e2e8f0;
}

[data-theme="dark"] .inline-viewer-container {
background-color: #0f172a;
}

[data-theme="dark"] .inline-viewer-toolbar {
background-color: #2a3042;
border-top-color: #334155;
}

[data-theme="dark"] .inline-viewer-controls button,
[data-theme="dark"] button.inline-viewer-fullscreen {
background-color: #334155;
border-color: #475569;
color: #94a3b8;
}

[data-theme="dark"] .inline-viewer-controls button:hover,
[data-theme="dark"] button.inline-viewer-fullscreen:hover {
background-color: #475569;
color: #e2e8f0;
}

[data-theme="dark"] .inline-viewer-text-content {
background-color: #1e293b;
color: #e2e8f0;
}

[data-theme="dark"] .inline-viewer-icon {
color: #475569;
}

[data-theme="dark"] .inline-viewer-message .inline-viewer-text {
color: #94a3b8;
}

[data-theme="dark"] .inline-viewer-audio-icon {
color: #64748b;
}

[data-theme="dark"] .inline-viewer-audio-name {
color: #94a3b8;
}

/* Responsive adjustments */
@media (max-width: 768px) {
.inline-viewer-content {
Expand Down
127 changes: 127 additions & 0 deletions static/css/views/markdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* Markdown viewer — GitHub-like typography */
.inline-viewer-markdown {
width: 100%;
height: 100%;
overflow: auto;
padding: 32px 48px;
box-sizing: border-box;
background: #fff;
color: #1f2937;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-size: 16px;
line-height: 1.7;
text-align: left;
}

.inline-viewer-markdown h1 {
font-size: 2em;
font-weight: 700;
margin: 0 0 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}

.inline-viewer-markdown h2 {
font-size: 1.5em;
font-weight: 600;
margin: 24px 0 12px;
padding-bottom: 6px;
border-bottom: 1px solid #e5e7eb;
}

.inline-viewer-markdown h3 { font-size: 1.25em; font-weight: 600; margin: 20px 0 10px; }
.inline-viewer-markdown h4 { font-size: 1em; font-weight: 600; margin: 16px 0 8px; }
.inline-viewer-markdown p { margin: 0 0 16px; }
.inline-viewer-markdown a { color: #2563eb; text-decoration: none; }
.inline-viewer-markdown a:hover { text-decoration: underline; }
.inline-viewer-markdown strong { font-weight: 600; }

.inline-viewer-markdown blockquote {
margin: 0 0 16px;
padding: 4px 16px;
border-left: 4px solid #d1d5db;
color: #6b7280;
}

.inline-viewer-markdown ul,
.inline-viewer-markdown ol {
margin: 0 0 16px;
padding-left: 2em;
}

.inline-viewer-markdown li { margin-bottom: 4px; }
.inline-viewer-markdown li > input[type="checkbox"] { margin-right: 6px; pointer-events: none; }

.inline-viewer-markdown code {
font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.875em;
background: #f3f4f6;
padding: 2px 6px;
border-radius: 4px;
}

.inline-viewer-markdown pre {
margin: 0 0 16px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
overflow-x: auto;
line-height: 1.5;
}

.inline-viewer-markdown pre code {
background: none;
padding: 0;
font-size: 0.875em;
}

.inline-viewer-markdown table {
width: 100%;
border-collapse: collapse;
margin: 0 0 16px;
font-size: 0.9em;
}

.inline-viewer-markdown th,
.inline-viewer-markdown td {
padding: 8px 12px;
border: 1px solid #d1d5db;
text-align: left;
}

.inline-viewer-markdown th { font-weight: 600; background: #f9fafb; }
.inline-viewer-markdown tr:nth-child(even) { background: #f9fafb; }

.inline-viewer-markdown hr {
height: 2px;
background: #e5e7eb;
border: none;
margin: 24px 0;
}

.inline-viewer-markdown img {
max-width: 100%;
height: auto;
border-radius: 6px;
}

.inline-viewer-markdown del { color: #9ca3af; }

@media (max-width: 768px) {
.inline-viewer-markdown { padding: 16px 20px; font-size: 15px; }
}

/* Dark mode */
[data-theme="dark"] .inline-viewer-markdown { background: #1e293b; color: #e2e8f0; }
[data-theme="dark"] .inline-viewer-markdown h1,
[data-theme="dark"] .inline-viewer-markdown h2 { border-bottom-color: #334155; }
[data-theme="dark"] .inline-viewer-markdown a { color: #60a5fa; }
[data-theme="dark"] .inline-viewer-markdown blockquote { border-left-color: #475569; color: #94a3b8; }
[data-theme="dark"] .inline-viewer-markdown code { background: #334155; }
[data-theme="dark"] .inline-viewer-markdown pre { background: #0f172a; }
[data-theme="dark"] .inline-viewer-markdown th { background: #334155; }
[data-theme="dark"] .inline-viewer-markdown tr:nth-child(even) { background: #1a2332; }
[data-theme="dark"] .inline-viewer-markdown th,
[data-theme="dark"] .inline-viewer-markdown td { border-color: #334155; }
[data-theme="dark"] .inline-viewer-markdown hr { background: #334155; }
[data-theme="dark"] .inline-viewer-markdown del { color: #64748b; }
1 change: 1 addition & 0 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<!-- Styles -->
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/views/inlineViewer.css">
<link rel="stylesheet" href="/css/views/markdown.css">
<link rel="stylesheet" href="/css/views/favorites.css">
<link rel="stylesheet" href="/css/views/recent.css">
<link rel="stylesheet" href="/css/views/shared.css">
Expand Down
9 changes: 5 additions & 4 deletions static/js/app/filesView.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ async function loadFiles(options = {}) {

if (response.status === 401 || response.status === 403) {
console.warn("Auth error when loading files, showing empty list");
const _te = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop();
elements.filesGrid.innerHTML = '<div class="empty-state"><p>Could not load files</p></div>';
elements.filesListView.innerHTML = `
<div class="list-header">
<div class="list-header-checkbox"><input type="checkbox" id="select-all-checkbox" title="Select all"></div>
<div>Name</div>
<div>Type</div>
<div>Size</div>
<div>Modified</div>
<div data-i18n="files.name">${_te('files.name')}</div>
<div data-i18n="files.type">${_te('files.type')}</div>
<div data-i18n="files.size">${_te('files.size')}</div>
<div data-i18n="files.modified">${_te('files.modified')}</div>
</div>
`;
return;
Expand Down
2 changes: 1 addition & 1 deletion static/js/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ function setupEventListeners() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
if (filesGrid) filesGrid.style.display = app.currentView === 'grid' ? 'grid' : 'none';
if (filesListView) filesListView.style.display = app.currentView === 'list' ? 'block' : 'none';
if (filesListView) filesListView.style.display = app.currentView === 'list' ? 'flex' : 'none';

// Update UI
elements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.trash') : 'Trash';
Expand Down
6 changes: 3 additions & 3 deletions static/js/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function switchToFilesView() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'flex' : 'none';

// Reset to home folder and update breadcrumb
window.app.currentPath = window.app.userHomeFolderId || '';
Expand All @@ -180,7 +180,7 @@ function switchToFavoritesView() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'flex' : 'none';

if (window.favorites) {
window.favorites.displayFavorites();
Expand Down Expand Up @@ -211,7 +211,7 @@ function switchToRecentFilesView() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
if (filesGrid) filesGrid.style.display = window.app.currentView === 'grid' ? 'grid' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'block' : 'none';
if (filesListView) filesListView.style.display = window.app.currentView === 'list' ? 'flex' : 'none';

if (window.recent) {
window.recent.displayRecentFiles();
Expand Down
10 changes: 6 additions & 4 deletions static/js/app/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,11 +757,13 @@ const ui = {
const ext = (file.name || '').split('.').pop().toLowerCase();
const imageExts = ['jpg','jpeg','png','gif','svg','webp','bmp','ico','heic','heif','avif','tiff'];
const isImage = (file.mime_type && file.mime_type.startsWith('image/')) || imageExts.includes(ext);
if (!isImage && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) {
const isPdf = ext === 'pdf';
const isViewable = self.isViewableFile(file) || isImage;
if (!isViewable && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) {
window.wopiEditor.openInModal(file.id, file.name, 'edit');
return;
}
if (self.isViewableFile(file) || isImage) {
if (isViewable) {
if (window.inlineViewer) window.inlineViewer.openFile(file);
else window.fileOps.downloadFile(file.id, file.name);
} else {
Expand Down Expand Up @@ -1079,7 +1081,7 @@ const ui = {
<i class="fas fa-folder"></i>
</div>
<div class="file-name">${escapeHtml(folder.name)}</div>
<div class="file-info">Folder</div>
<div class="file-info">${window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder'}</div>
`;

if (window.app.currentPath !== "") {
Expand Down Expand Up @@ -1147,7 +1149,7 @@ const ui = {
<i class="${iconClass}"></i>
</div>
<div class="file-name">${escapeHtml(file.name)}</div>
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
<div class="file-info">${window.i18n ? window.i18n.t('files.modified') : 'Modified'} ${formattedDate.split(' ')[0]}</div>
`;
var thumb = el.querySelector('.file-thumb');
if (thumb) thumb.addEventListener('error', function() { this.style.display = 'none'; });
Expand Down
Loading