Merge pull request #300 from EdouardVanbelle/feat/show-shared
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
<div class="nav-item active">
|
<div class="nav-item">
|
||||||
<i class="fas fa-folder"></i>
|
<i class="fas fa-folder"></i>
|
||||||
<span data-i18n="nav.files">Files</span>
|
<span data-i18n="nav.files">Files</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(`
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user