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/index.html b/static/index.html
index dc5264b6..d0000a75 100644
--- a/static/index.html
+++ b/static/index.html
@@ -70,7 +70,7 @@
${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