Files
Oxicloud/static/shared.html
T
Diocrafts a1a3bd1b2b fix: folder trash/delete operations & frontend refactoring
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' does not exist)
- Fix folder deletion: delete descendant files before folder to avoid 'duplicate key violates unique constraint idx_files_unique_name_at_root'
- Simplify trash model: only mark the folder as trashed, not child files (implicit trash via parent)
- Update trash_items view: filter to show only top-level trashed items
- Update schema.sql: change files.folder_id FK from ON DELETE SET NULL to ON DELETE CASCADE
- Fix trash view icons: folders and files now show correct visual icons (folder-icon, pdf-icon, etc.) in trash view
- Frontend refactoring: extract inline CSS/JS from admin.html and profile.html into dedicated external files
- Frontend cleanup: replace all inline style attributes with CSS classes
- Frontend cleanup: replace style.display JS
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' d
2026-02-20 12:27:52 +01:00

313 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OxiCloud - Shared Resources</title>
<!-- Apply saved theme immediately to prevent flash of light mode -->
<script>if(localStorage.getItem('oxicloud_theme')==='dark')document.documentElement.setAttribute('data-theme','dark');</script>
<link rel="stylesheet" href="/css/style.css">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<script src="/js/core/icons.js" defer></script>
</head>
<body>
<!-- Main layout similar to index.html -->
<div class="sidebar">
<a href="/" class="logo-container" style="text-decoration:none;color:inherit;">
<div class="logo">
<svg viewBox="0 0 500 500">
<path d="M345 310c32 0 58-26 58-58s-26-58-58-58c-6.2 0-12 0.9-17.5 2.7C318 166 289 143 255 143c-34.3 0-63.1 22.6-73 53.7C176.9 195.7 171 195 165 195c-32 0-58 26-58 58s26 58 58 58h180z" fill="#fff"/>
</svg>
</div>
<div class="app-name">OxiCloud</div>
</a>
<div class="nav-menu">
<a href="/" class="nav-item">
<i class="fas fa-folder"></i>
<span data-i18n="nav.files">Files</span>
</a>
<a href="/shared" class="nav-item active">
<i class="fas fa-share-alt"></i>
<span data-i18n="nav.shared">Shared</span>
</a>
<a href="/#recent" class="nav-item">
<i class="fas fa-clock"></i>
<span data-i18n="nav.recent">Recent</span>
</a>
<a href="/#favorites" class="nav-item">
<i class="fas fa-star"></i>
<span data-i18n="nav.favorites">Favorites</span>
</a>
<a href="/#trash" class="nav-item">
<i class="fas fa-trash"></i>
<span data-i18n="nav.trash">Trash</span>
</a>
</div>
<div class="storage-container">
<div class="storage-title"><i class="fas fa-database" style="margin-right:6px;color:#ff5e3a"></i><span data-i18n="storage.title">Storage</span></div>
<div class="storage-bar">
<div class="storage-fill"></div>
</div>
<div class="storage-info" data-i18n="storage.calculating">Calculating...</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Top Bar -->
<div class="top-bar">
<div class="search-container">
<i class="fas fa-search search-icon"></i>
<input type="text" id="shared-search" data-i18n-placeholder="actions.search" placeholder="Search shared items...">
<button id="shared-search-btn" class="search-button" data-i18n-title="actions.search_btn" title="Search">
<i class="fas fa-search"></i>
</button>
</div>
<div class="user-controls">
<div id="language-selector"></div>
<div class="user-menu-wrapper">
<div class="user-avatar" id="user-avatar">AD</div>
<div class="user-menu" id="user-menu">
<div class="user-menu-header">
<span class="user-name" id="user-menu-name">Admin</span>
<span class="user-email" id="user-menu-email">admin@oxicloud.local</span>
</div>
<div class="user-menu-divider"></div>
<div class="user-menu-item" id="menu-theme">
<i class="fas fa-moon"></i>
<span data-i18n="user_menu.appearance">Appearance</span>
<div class="theme-toggle-pill" id="theme-toggle-pill">
<div class="theme-toggle-knob"></div>
</div>
</div>
<div class="user-menu-item" id="menu-logout">
<i class="fas fa-sign-out-alt"></i>
<span data-i18n="actions.logout">Log out</span>
</div>
</div>
</div>
</div>
</div>
<div class="content-area">
<h1 class="page-title" data-i18n="shared.pageTitle">Shared Resources</h1>
<p class="page-description" data-i18n="shared.pageDescription">Manage your shared files and folders</p>
<div class="shared-filters">
<div class="filter-group">
<label for="filter-type" data-i18n="shared.filterType">Type:</label>
<select id="filter-type">
<option value="all" data-i18n="shared.filterAll">All</option>
<option value="file" data-i18n="shared.filterFiles">Files</option>
<option value="folder" data-i18n="shared.filterFolders">Folders</option>
</select>
</div>
<div class="filter-group">
<label for="sort-by" data-i18n="shared.sortBy">Sort by:</label>
<select id="sort-by">
<option value="name" data-i18n="shared.sortByName">Name</option>
<option value="date" data-i18n="shared.sortByDate">Date shared</option>
<option value="expiration" data-i18n="shared.sortByExpiration">Expiration</option>
</select>
</div>
</div>
<div class="shared-list-container">
<table class="shared-list">
<thead>
<tr>
<th data-i18n="shared.colName">Name</th>
<th data-i18n="shared.colType">Type</th>
<th data-i18n="shared.colDateShared">Date Shared</th>
<th data-i18n="shared.colExpiration">Expiration</th>
<th data-i18n="shared.colPermissions">Permissions</th>
<th data-i18n="shared.colPassword">Password</th>
<th data-i18n="shared.colActions">Actions</th>
</tr>
</thead>
<tbody id="shared-items-list">
<!-- Shared items will be loaded here dynamically -->
</tbody>
</table>
</div>
<div id="empty-shared-state" class="empty-state">
<div class="empty-state-icon">📂</div>
<h3 data-i18n="shared.emptyStateTitle">No shared resources yet</h3>
<p data-i18n="shared.emptyStateDesc">When you share files or folders, they will appear here</p>
<a href="/" class="button primary" data-i18n="shared.goToFiles">Go to Files</a>
</div>
</div>
</div>
<!-- Share Link Dialog (for editing existing shares) -->
<div id="share-dialog" class="dialog">
<div class="dialog-content">
<div class="dialog-header">
<h3 data-i18n="share.dialogTitle">Share Link</h3>
<button class="close-dialog-btn">&times;</button>
</div>
<div class="dialog-body">
<div class="share-item-info">
<span id="share-dialog-icon" class="item-icon">📄</span>
<span id="share-dialog-name" class="item-name">filename.ext</span>
</div>
<div class="share-link-section">
<label for="share-link-url" data-i18n="share.linkLabel">Share Link:</label>
<div class="share-link-container">
<input type="text" id="share-link-url" readonly>
<button id="copy-link-btn" data-i18n="share.copyLink">Copy</button>
</div>
</div>
<div class="share-settings">
<div class="share-setting">
<label data-i18n="share.permissions">Permissions:</label>
<div class="permissions-options">
<label>
<input type="checkbox" id="permission-read" checked>
<span data-i18n="share.permissionRead">Read</span>
</label>
<label>
<input type="checkbox" id="permission-write">
<span data-i18n="share.permissionWrite">Write</span>
</label>
<label>
<input type="checkbox" id="permission-reshare">
<span data-i18n="share.permissionReshare">Reshare</span>
</label>
</div>
</div>
<div class="share-setting">
<label for="share-password" data-i18n="share.password">Password Protection:</label>
<div class="password-setting">
<input type="checkbox" id="enable-password">
<input type="password" id="share-password" placeholder="Enter password" disabled>
<button id="generate-password" data-i18n="share.generatePassword">Generate</button>
</div>
</div>
<div class="share-setting">
<label for="share-expiration" data-i18n="share.expiration">Expiration Date:</label>
<div class="expiration-setting">
<input type="checkbox" id="enable-expiration">
<input type="date" id="share-expiration" disabled>
</div>
</div>
</div>
<div class="share-actions">
<button id="update-share-btn" class="button primary" data-i18n="share.update">Update Share</button>
<button id="remove-share-btn" class="button danger" data-i18n="share.remove">Remove Share</button>
</div>
</div>
</div>
</div>
<!-- Email Notification Dialog -->
<div id="share-notification-dialog" class="dialog">
<div class="dialog-content">
<div class="dialog-header">
<h3 data-i18n="share.notifyTitle">Send Notification</h3>
<button class="close-dialog-btn">&times;</button>
</div>
<div class="dialog-body">
<div class="share-item-info">
<span id="notify-dialog-icon" class="item-icon">📄</span>
<span id="notify-dialog-name" class="item-name">filename.ext</span>
</div>
<div class="notification-form">
<div class="form-group">
<label for="notification-email" data-i18n="share.notifyEmailLabel">Email Address:</label>
<input type="email" id="notification-email" placeholder="Enter recipient email">
</div>
<div class="form-group">
<label for="notification-message" data-i18n="share.notifyMessageLabel">Message (optional):</label>
<textarea id="notification-msg-text" placeholder="Add a personal message" rows="3"></textarea>
</div>
</div>
<div class="notification-actions">
<button id="send-notification-btn" class="button primary" data-i18n="share.notifySend">Send Notification</button>
</div>
</div>
</div>
</div>
<!-- Notification Banner -->
<div id="notification-banner" class="notification-banner">
<span id="notification-message"></span>
<button id="close-notification" class="close-notification-btn">×</button>
</div>
<script src="/js/core/i18n.js"></script>
<script src="/js/core/languageSelector.js"></script>
<script src="/js/core/formatters.js"></script>
<script src="/js/features/sharing/fileSharing.js"></script>
<script>
// Auth check — redirect to login if no token
(function() {
const token = localStorage.getItem('oxicloud_token');
if (!token) {
window.location.href = '/login';
return;
}
// Set user avatar initials
const userData = JSON.parse(localStorage.getItem('oxicloud_user') || '{}');
const avatarEl = document.getElementById('user-avatar');
const nameEl = document.getElementById('user-menu-name');
const emailEl = document.getElementById('user-menu-email');
if (userData.username && avatarEl) {
const initials = userData.username.substring(0, 2).toUpperCase();
avatarEl.textContent = initials;
}
if (nameEl) nameEl.textContent = userData.username || 'User';
if (emailEl) emailEl.textContent = userData.email || '';
// User menu toggle
if (avatarEl) {
avatarEl.addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('user-menu').classList.toggle('active');
});
}
document.addEventListener('click', () => {
const menu = document.getElementById('user-menu');
if (menu) menu.classList.remove('active');
});
const logoutBtn = document.getElementById('menu-logout');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => {
localStorage.removeItem('oxicloud_token');
localStorage.removeItem('oxicloud_user');
window.location.href = '/login';
});
}
// Theme toggle (dark mode)
const themeBtn = document.getElementById('menu-theme');
const pill = document.getElementById('theme-toggle-pill');
if (themeBtn && pill) {
const isDark = localStorage.getItem('oxicloud_theme') === 'dark';
if (isDark) pill.classList.add('active');
themeBtn.addEventListener('click', (e) => {
e.stopPropagation();
pill.classList.toggle('active');
const dark = pill.classList.contains('active');
localStorage.setItem('oxicloud_theme', dark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
});
}
})();
</script>
<script src="/js/views/shared/shared.js"></script>
</body>
</html>