a1a3bd1b2b
- 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
313 lines
15 KiB
HTML
313 lines
15 KiB
HTML
<!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">×</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">×</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>
|