feat(P1+P2): server-authoritative favorites/recent + pre-computed display fields

P1-A: Enrich FavoriteItemDto & RecentItemDto with item_name, item_size,
      item_mime_type, parent_id, modified_at via SQL LEFT JOINs — eliminates
      N+1 per-item fetches.

P1-B: Rewrite favorites.js as server-authoritative (724→380 lines).
      In-memory cache backed by GET /api/favorites; no localStorage.

P1-C: Rewrite recent.js as server-authoritative (341→249 lines).
      GET /api/recent + POST /api/recent/{type}/{id}; no localStorage.

P1 cleanup: Remove dead localStorage cleanup from auth.js logout,
            fix async clearRecentFiles in app.js.

P2: Add icon_class, icon_special_class, category, size_formatted to
    FileDto, FolderDto, FavoriteItemDto, RecentItemDto. New shared
    display_helpers.rs module centralises mime→icon/category/size logic.
    Frontend (ui.js, fileRenderer.js, favorites.js, recent.js) now reads
    pre-computed fields from the API with fallback defaults — eliminates
    5 duplicated mime→icon mapping blocks (~89 lines removed).

Also fixes: synthetic FolderDto in webdav_handler.rs, pre-existing
            missing item_name field in share_service.rs test.

Net: -307 lines across 15 files. cargo check: 0 errors, 0 warnings.
     cargo test display_helpers: 3/3 pass.
This commit is contained in:
Dionisio
2026-02-16 01:09:28 +01:00
parent 5a679dfc90
commit fb652c07e3
16 changed files with 802 additions and 983 deletions
+7 -29
View File
@@ -903,37 +903,15 @@ const ui = {
console.log(`Adding file to the view: ${file.name} (${file.id})`);
// Determine icon and type
let iconClass = 'fas fa-file';
let iconSpecialClass = '';
let typeLabel = 'Document';
if (file.mime_type) {
if (file.mime_type.startsWith('image/')) {
iconClass = 'fas fa-file-image';
iconSpecialClass = 'image-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.image') : 'Image';
} else if (file.mime_type.startsWith('text/')) {
iconClass = 'fas fa-file-alt';
iconSpecialClass = 'text-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.text') : 'Text';
} else if (file.mime_type.startsWith('video/')) {
iconClass = 'fas fa-file-video';
iconSpecialClass = 'video-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.video') : 'Video';
} else if (file.mime_type.startsWith('audio/')) {
iconClass = 'fas fa-file-audio';
iconSpecialClass = 'audio-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.audio') : 'Audio';
} else if (file.mime_type === 'application/pdf') {
iconClass = 'fas fa-file-pdf';
iconSpecialClass = 'pdf-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.pdf') : 'PDF';
}
}
// Use pre-computed display fields from the API response
const iconClass = file.icon_class || 'fas fa-file';
const iconSpecialClass = file.icon_special_class || '';
const typeLabel = file.category
? (window.i18n ? window.i18n.t(`files.file_types.${file.category.toLowerCase()}`) || file.category : file.category)
: (window.i18n ? window.i18n.t('files.file_types.document') : 'Document');
// Format size and date
const fileSize = window.formatFileSize(file.size);
const fileSize = file.size_formatted || window.formatFileSize(file.size);
const modifiedDate = new Date(file.modified_at * 1000);
const formattedDate = modifiedDate.toLocaleDateString() + ' ' +
modifiedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});