feat(shared): show in classic view if an item is shared

This commit is contained in:
Edouard Vanbelle
2026-04-16 10:10:09 +02:00
parent c8e67f77ea
commit ceff552736
6 changed files with 94 additions and 21 deletions
+26 -2
View File
@@ -61,6 +61,10 @@
background-color: var(--color-warning-ring); background-color: var(--color-warning-ring);
} }
.file-badge-shared {
color: var(--color-badge-blue-text);
}
/* ------------------File list View --------------------- */ /* ------------------File list View --------------------- */
.list-header { .list-header {
@@ -95,7 +99,7 @@
} }
.files-list-view { .files-list-view {
--files-list-columns: 36px minmax(200px, 2fr) 100px 110px 130px 64px; --files-list-columns: 36px minmax(200px, 2fr) 100px 110px 130px 72px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@@ -174,7 +178,8 @@
text-align: right; text-align: right;
} }
.files-list-view .file-item .action-cell button { .files-list-view .file-item .action-cell button,
.files-list-view .file-item .action-cell div {
display: inline; display: inline;
width: 28px; width: 28px;
height: 28px; height: 28px;
@@ -327,6 +332,25 @@
border: 2px dashed var(--color-warning-border); border: 2px dashed var(--color-warning-border);
} }
.files-grid-view .file-item .file-badge-shared {
position: absolute;
top: 38px;
right: 38px;
width: 30px;
height: 30px;
border-radius: 8px;
border: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
z-index: 12;
font-size: 15px;
padding: 0;
line-height: 1;
}
/* Favorite star (mirrors file-actions button pattern) */ /* Favorite star (mirrors file-actions button pattern) */
.files-grid-view .file-item button.favorite-star { .files-grid-view .file-item button.favorite-star {
position: absolute; position: absolute;
+3 -1
View File
@@ -376,7 +376,7 @@ function initApp() {
multiSelect.init(); multiSelect.init();
} }
window.addEventListener('authenticationDone', () => { window.addEventListener('authenticationDone', async () => {
// Check if a context was provided in the URL // Check if a context was provided in the URL
const hashContext = deserializeHash(); const hashContext = deserializeHash();
switchSectionTo(hashContext.section); switchSectionTo(hashContext.section);
@@ -389,6 +389,7 @@ function initApp() {
if (hashContext.file !== null) { if (hashContext.file !== null) {
app.viewFile = hashContext.file; app.viewFile = hashContext.file;
} }
loadFiles(); loadFiles();
} }
}); });
@@ -604,6 +605,7 @@ function setupEventListeners() {
let _updateHistory = true; let _updateHistory = true;
const itemI18nKey = item.querySelector('span').getAttribute('data-i18n'); const itemI18nKey = item.querySelector('span').getAttribute('data-i18n');
switch (itemI18nKey) { switch (itemI18nKey) {
case 'nav.shared': case 'nav.shared':
// Switch to shared view // Switch to shared view
+12 -4
View File
@@ -190,10 +190,10 @@ function switchToSharedSection() {
toggleFileContainer(false); toggleFileContainer(false);
// Show shared view // Show shared view
if (sharedView) { sharedView.init().then(() => {
sharedView.init();
sharedView.show(); sharedView.show();
} });
if (multiSelect) multiSelect.clear(); if (multiSelect) multiSelect.clear();
} }
@@ -222,7 +222,10 @@ function switchToFilesSection() {
ui.updateBreadcrumb(); ui.updateBreadcrumb();
if (multiSelect) multiSelect.clear(); if (multiSelect) multiSelect.clear();
// temp solution
sharedView.loadItems().then(() => {
loadFiles(); loadFiles();
});
} }
function switchToFavoritesSection() { function switchToFavoritesSection() {
@@ -245,7 +248,10 @@ function switchToFavoritesSection() {
ui.resetFilesList(); ui.resetFilesList();
if (favorites) { if (favorites) {
// temp solution
sharedView.loadItems().then(() => {
favorites.displayFavorites(); favorites.displayFavorites();
});
} else { } else {
console.error('Favorites module not loaded or initialized'); console.error('Favorites module not loaded or initialized');
ui.showError(` ui.showError(`
@@ -277,7 +283,9 @@ function switchToRecentFilesSection() {
ui.resetFilesList(); ui.resetFilesList();
if (recent) { if (recent) {
recent.displayRecentFiles(); sharedView.loadItems().then(() => {
favorites.displayFavorites();
});
} else { } else {
console.error('Recent files module not loaded or initialized'); console.error('Recent files module not loaded or initialized');
ui.showError(` ui.showError(`
+6
View File
@@ -16,6 +16,7 @@ import { wopiEditor } from '../features/files/wopiEditor.js';
import { favorites } from '../features/library/favorites.js'; import { favorites } from '../features/library/favorites.js';
import { recent } from '../features/library/recent.js'; import { recent } from '../features/library/recent.js';
import { fileSharing } from '../features/sharing/fileSharing.js'; import { fileSharing } from '../features/sharing/fileSharing.js';
import { sharedView } from '../views/shared/sharedView.js';
import { loadFiles } from './filesView.js'; import { loadFiles } from './filesView.js';
import { updateHistory } from './main.js'; import { updateHistory } from './main.js';
import { syncViewContainers } from './navigation.js'; import { syncViewContainers } from './navigation.js';
@@ -1209,6 +1210,7 @@ const ui = {
el.dataset.parentId = folder.parent_id || ''; el.dataset.parentId = folder.parent_id || '';
const isFav = favorites?.isFavorite(folder.id, 'folder'); const isFav = favorites?.isFavorite(folder.id, 'folder');
const isShared = sharedView.isShared(folder.id, 'folder');
const formattedDate = formatDateTime(folder.modified_at); const formattedDate = formatDateTime(folder.modified_at);
el.innerHTML = ` el.innerHTML = `
@@ -1219,6 +1221,7 @@ const ui = {
</div> </div>
<span>${escapeHtml(folder.name)}</span> <span>${escapeHtml(folder.name)}</span>
${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''} ${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''}
${isShared ? '<div class="file-badge-shared"><i class="fas fa-share-alt"></i></div>' : ''}
</div> </div>
<div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div> <div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div>
<div class="size-cell">--</div> <div class="size-cell">--</div>
@@ -1247,6 +1250,7 @@ const ui = {
const fileSize = file.size_formatted || formatFileSize(file.size); const fileSize = file.size_formatted || formatFileSize(file.size);
const formattedDate = formatDateTime(file.modified_at); const formattedDate = formatDateTime(file.modified_at);
const isFav = favorites?.isFavorite(file.id, 'file'); const isFav = favorites?.isFavorite(file.id, 'file');
const isShared = sharedView.isShared(file.id, 'file');
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'file-item'; el.className = 'file-item';
@@ -1265,6 +1269,8 @@ const ui = {
</div> </div>
<span>${escapeHtml(file.name)}</span> <span>${escapeHtml(file.name)}</span>
${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''} ${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''}
${isShared ? '<div class="file-badge-shared"><i class="fas fa-share-alt"></i></div>' : ''}
</div> </div>
<div class="type-cell">${typeLabel}</div> <div class="type-cell">${typeLabel}</div>
<div class="size-cell">${fileSize}</div> <div class="size-cell">${fileSize}</div>
+40 -8
View File
@@ -10,9 +10,17 @@ import { formatDateShort } from '../../core/formatters.js';
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
import { fileSharing } from '../../features/sharing/fileSharing.js'; import { fileSharing } from '../../features/sharing/fileSharing.js';
const TTL = 5 * 60 * 1000; // 5 min
const sharedView = { const sharedView = {
// State // State
items: [], items: [],
_expires: 0,
/** @type {Map<string, boolean>} key = "file:<id>" | "folder:<id>" */
_knownItemsId: new Map(),
filteredItems: [], filteredItems: [],
currentItem: null, currentItem: null,
@@ -23,15 +31,15 @@ const sharedView = {
return h; return h;
}, },
init() { async init() {
console.log('Initializing shared view component (API-backed)'); console.log('Initializing shared view component (API-backed)');
this.loadItems(); await this.loadItems();
}, },
show() { show() {
this.displayUI(); this.displayUI();
this.attachEventListeners(); this.attachEventListeners();
this.loadItems().then(() => this.filterAndSortItems()); this.filterAndSortItems();
const c = document.getElementById('shared-container'); const c = document.getElementById('shared-container');
if (c) c.classList.remove('hidden'); if (c) c.classList.remove('hidden');
}, },
@@ -41,8 +49,27 @@ const sharedView = {
if (c) c.classList.add('hidden'); if (c) c.classList.add('hidden');
}, },
// Load shared items from backend API /**
async loadItems() { * tells if item_id is shared
*
* @param {string} id the item_id
* @param {string} type folder|file
* @returns {boolean} true if this item is shared
*/
isShared(id, type) {
return this._knownItemsId.has(`${type}:${id}`);
},
// Load shared items from backend API,
// TODO cache entries to minimize calls
/**
* load shared items
*
* @param {boolean} force ignore cache
*/
async loadItems(force) {
if (this._expires > Date.now() && !force) return;
try { try {
const res = await fetch('/api/shares?page=1&per_page=1000', { const res = await fetch('/api/shares?page=1&per_page=1000', {
headers: this._headers() headers: this._headers()
@@ -53,11 +80,16 @@ const sharedView = {
} else { } else {
this.items = []; this.items = [];
} }
this.filteredItems = { ...this.items };
this._knownItemsId.clear();
this.items.forEach((item) => {
this._knownItemsId.set(`${item.item_type}:${item.item_id}`, true);
});
this._expires = Date.now() + TTL;
} catch (err) { } catch (err) {
console.error('Error loading shared items:', err); console.error('Error loading shared items:', err);
this.items = []; this.items = [];
} }
this.filteredItems = [...this.items];
}, },
// Create and display the shared view UI // Create and display the shared view UI
@@ -559,7 +591,7 @@ const sharedView = {
} }
this.closeShareDialog(); this.closeShareDialog();
await this.loadItems(); await this.loadItems(true);
this.filterAndSortItems(); this.filterAndSortItems();
}, },
@@ -580,7 +612,7 @@ const sharedView = {
} }
this.closeShareDialog(); this.closeShareDialog();
await this.loadItems(); await this.loadItems(true);
this.filterAndSortItems(); this.filterAndSortItems();
}, },
+2 -1
View File
@@ -1,5 +1,6 @@
// OxiCloud Service Worker // OxiCloud Service Worker
const CACHE_NAME = 'oxicloud-cache-v17'; // FIXME: generate cache name according build ?
const CACHE_NAME = 'oxicloud-cache-v19';
// Only cache static assets — NOT HTML files. // Only cache static assets — NOT HTML files.
// HTML files are served network-first so browsers always get the latest // HTML files are served network-first so browsers always get the latest