From ceff552736437d9402c6f7db8cce3b3a2f388441 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Thu, 16 Apr 2026 10:10:09 +0200 Subject: [PATCH] feat(shared): show in classic view if an item is shared --- static/css/components/filesView.css | 28 ++++++++++++++-- static/js/app/main.js | 4 ++- static/js/app/navigation.js | 20 ++++++++---- static/js/app/ui.js | 12 +++++-- static/js/views/shared/sharedView.js | 48 +++++++++++++++++++++++----- static/sw.js | 3 +- 6 files changed, 94 insertions(+), 21 deletions(-) diff --git a/static/css/components/filesView.css b/static/css/components/filesView.css index 523dc6b8..5ac0c7ee 100644 --- a/static/css/components/filesView.css +++ b/static/css/components/filesView.css @@ -61,6 +61,10 @@ background-color: var(--color-warning-ring); } +.file-badge-shared { + color: var(--color-badge-blue-text); +} + /* ------------------File list View --------------------- */ .list-header { @@ -95,7 +99,7 @@ } .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; flex-direction: column; width: 100%; @@ -174,7 +178,8 @@ 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; width: 28px; height: 28px; @@ -327,6 +332,25 @@ 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) */ .files-grid-view .file-item button.favorite-star { position: absolute; diff --git a/static/js/app/main.js b/static/js/app/main.js index 229138d9..47801b90 100644 --- a/static/js/app/main.js +++ b/static/js/app/main.js @@ -376,7 +376,7 @@ function initApp() { multiSelect.init(); } - window.addEventListener('authenticationDone', () => { + window.addEventListener('authenticationDone', async () => { // Check if a context was provided in the URL const hashContext = deserializeHash(); switchSectionTo(hashContext.section); @@ -389,6 +389,7 @@ function initApp() { if (hashContext.file !== null) { app.viewFile = hashContext.file; } + loadFiles(); } }); @@ -604,6 +605,7 @@ function setupEventListeners() { let _updateHistory = true; const itemI18nKey = item.querySelector('span').getAttribute('data-i18n'); + switch (itemI18nKey) { case 'nav.shared': // Switch to shared view diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js index 00b3c066..5db90cdb 100644 --- a/static/js/app/navigation.js +++ b/static/js/app/navigation.js @@ -190,10 +190,10 @@ function switchToSharedSection() { toggleFileContainer(false); // Show shared view - if (sharedView) { - sharedView.init(); + sharedView.init().then(() => { sharedView.show(); - } + }); + if (multiSelect) multiSelect.clear(); } @@ -222,7 +222,10 @@ function switchToFilesSection() { ui.updateBreadcrumb(); if (multiSelect) multiSelect.clear(); - loadFiles(); + // temp solution + sharedView.loadItems().then(() => { + loadFiles(); + }); } function switchToFavoritesSection() { @@ -245,7 +248,10 @@ function switchToFavoritesSection() { ui.resetFilesList(); if (favorites) { - favorites.displayFavorites(); + // temp solution + sharedView.loadItems().then(() => { + favorites.displayFavorites(); + }); } else { console.error('Favorites module not loaded or initialized'); ui.showError(` @@ -277,7 +283,9 @@ function switchToRecentFilesSection() { ui.resetFilesList(); if (recent) { - recent.displayRecentFiles(); + sharedView.loadItems().then(() => { + favorites.displayFavorites(); + }); } else { console.error('Recent files module not loaded or initialized'); ui.showError(` diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 1c3e7f83..53d18e88 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -16,6 +16,7 @@ import { wopiEditor } from '../features/files/wopiEditor.js'; import { favorites } from '../features/library/favorites.js'; import { recent } from '../features/library/recent.js'; import { fileSharing } from '../features/sharing/fileSharing.js'; +import { sharedView } from '../views/shared/sharedView.js'; import { loadFiles } from './filesView.js'; import { updateHistory } from './main.js'; import { syncViewContainers } from './navigation.js'; @@ -1209,6 +1210,7 @@ const ui = { el.dataset.parentId = folder.parent_id || ''; const isFav = favorites?.isFavorite(folder.id, 'folder'); + const isShared = sharedView.isShared(folder.id, 'folder'); const formattedDate = formatDateTime(folder.modified_at); el.innerHTML = ` @@ -1219,15 +1221,16 @@ const ui = { ${escapeHtml(folder.name)} ${isFav ? '' : ''} + ${isShared ? '
' : ''}
${i18n ? i18n.t('files.file_types.folder') : 'Folder'}
--
${formattedDate}
- - + +
`; @@ -1247,6 +1250,7 @@ const ui = { const fileSize = file.size_formatted || formatFileSize(file.size); const formattedDate = formatDateTime(file.modified_at); const isFav = favorites?.isFavorite(file.id, 'file'); + const isShared = sharedView.isShared(file.id, 'file'); const el = document.createElement('div'); el.className = 'file-item'; @@ -1265,6 +1269,8 @@ const ui = { ${escapeHtml(file.name)} ${isFav ? '' : ''} + ${isShared ? '
' : ''} +
${typeLabel}
${fileSize}
diff --git a/static/js/views/shared/sharedView.js b/static/js/views/shared/sharedView.js index 55ab6911..3b2d9c52 100644 --- a/static/js/views/shared/sharedView.js +++ b/static/js/views/shared/sharedView.js @@ -10,9 +10,17 @@ import { formatDateShort } from '../../core/formatters.js'; import { i18n } from '../../core/i18n.js'; import { fileSharing } from '../../features/sharing/fileSharing.js'; +const TTL = 5 * 60 * 1000; // 5 min + const sharedView = { // State items: [], + + _expires: 0, + + /** @type {Map} key = "file:" | "folder:" */ + _knownItemsId: new Map(), + filteredItems: [], currentItem: null, @@ -23,15 +31,15 @@ const sharedView = { return h; }, - init() { + async init() { console.log('Initializing shared view component (API-backed)'); - this.loadItems(); + await this.loadItems(); }, show() { this.displayUI(); this.attachEventListeners(); - this.loadItems().then(() => this.filterAndSortItems()); + this.filterAndSortItems(); const c = document.getElementById('shared-container'); if (c) c.classList.remove('hidden'); }, @@ -41,8 +49,27 @@ const sharedView = { 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 { const res = await fetch('/api/shares?page=1&per_page=1000', { headers: this._headers() @@ -53,11 +80,16 @@ const sharedView = { } else { 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) { console.error('Error loading shared items:', err); this.items = []; } - this.filteredItems = [...this.items]; }, // Create and display the shared view UI @@ -559,7 +591,7 @@ const sharedView = { } this.closeShareDialog(); - await this.loadItems(); + await this.loadItems(true); this.filterAndSortItems(); }, @@ -580,7 +612,7 @@ const sharedView = { } this.closeShareDialog(); - await this.loadItems(); + await this.loadItems(true); this.filterAndSortItems(); }, diff --git a/static/sw.js b/static/sw.js index d7053aab..bad30e9b 100644 --- a/static/sw.js +++ b/static/sw.js @@ -1,5 +1,6 @@ // 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. // HTML files are served network-first so browsers always get the latest