search inside folders

This commit is contained in:
2025-05-31 12:24:48 +02:00
parent 4494ebdeb3
commit 45a1bc07c6
5 changed files with 54 additions and 18 deletions

View File

@@ -557,32 +557,49 @@ def download_zip(room_id):
@login_required @login_required
def search_room_files(room_id): def search_room_files(room_id):
""" """
Search for files in a room by name. Search for files in a room by name, including files in subfolders.
Args: Args:
room_id (int): ID of the room to search in room_id (int): ID of the room to search in
Returns: Returns:
JSON response containing matching files JSON response containing matching files with their full paths
""" """
room = Room.query.get_or_404(room_id) room = Room.query.get_or_404(room_id)
if not user_has_permission(room, 'can_view'): if not user_has_permission(room, 'can_view'):
abort(403) abort(403)
query = request.args.get('query', '').strip().lower() query = request.args.get('query', '').strip().lower()
# Search RoomFile for this room # Search RoomFile for this room
files = RoomFile.query.filter(RoomFile.room_id==room_id).all() files = RoomFile.query.filter(RoomFile.room_id==room_id, RoomFile.deleted==False).all()
matches = [] matches = []
for f in files: for f in files:
if query in f.name.lower(): # Create full path by combining folder path and filename
full_path = f"{f.path}/{f.name}" if f.path else f.name
if query in f.name.lower() or query in full_path.lower():
uploader_full_name = None
uploader_profile_pic = None
if f.uploader:
uploader_full_name = f.uploader.username
if getattr(f.uploader, 'last_name', None):
uploader_full_name += ' ' + f.uploader.last_name
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
matches.append({ matches.append({
'name': f.name, 'name': f.name,
'type': f.type, 'type': f.type,
'size': f.size if f.type == 'file' else '-', 'size': f.size if f.type == 'file' else '-',
'modified': f.modified, 'modified': f.modified,
'uploaded_by': f.uploader.username if f.uploader else None, 'uploaded_by': uploader_full_name,
'uploader_profile_pic': uploader_profile_pic,
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None, 'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
'path': f.path, 'path': f.path,
'full_path': full_path, # Add full path to the response
'starred': current_user in f.starred_by
}) })
return jsonify(matches) return jsonify(matches)
@room_files_bp.route('/<int:room_id>/move', methods=['POST']) @room_files_bp.route('/<int:room_id>/move', methods=['POST'])

View File

@@ -22,6 +22,7 @@ export class SearchManager {
this.roomManager = roomManager; this.roomManager = roomManager;
this.searchInput = document.getElementById('quickSearchInput'); this.searchInput = document.getElementById('quickSearchInput');
this.clearSearchBtn = document.getElementById('clearSearchBtn'); this.clearSearchBtn = document.getElementById('clearSearchBtn');
this.isSearching = false;
} }
/** /**
@@ -35,9 +36,11 @@ export class SearchManager {
// Create debounced search function // Create debounced search function
const debouncedSearch = this.debounce((searchTerm) => { const debouncedSearch = this.debounce((searchTerm) => {
if (searchTerm) { if (searchTerm) {
this.isSearching = true;
this.performSearch(searchTerm); this.performSearch(searchTerm);
this.clearSearchBtn.style.display = 'block'; this.clearSearchBtn.style.display = 'block';
} else { } else {
this.isSearching = false;
this.roomManager.fileManager.fetchFiles(); // Reset to show all files this.roomManager.fileManager.fetchFiles(); // Reset to show all files
this.clearSearchBtn.style.display = 'none'; this.clearSearchBtn.style.display = 'none';
} }
@@ -53,6 +56,7 @@ export class SearchManager {
if (this.clearSearchBtn) { if (this.clearSearchBtn) {
this.clearSearchBtn.addEventListener('click', () => { this.clearSearchBtn.addEventListener('click', () => {
this.searchInput.value = ''; this.searchInput.value = '';
this.isSearching = false;
this.roomManager.fileManager.fetchFiles(); // Reset to show all files this.roomManager.fileManager.fetchFiles(); // Reset to show all files
this.clearSearchBtn.style.display = 'none'; this.clearSearchBtn.style.display = 'none';
}); });
@@ -60,20 +64,25 @@ export class SearchManager {
} }
/** /**
* Performs the search operation on the current file list. * Performs the search operation using the server-side search endpoint.
* Filters files based on name and type matching the search term.
* @param {string} searchTerm - The term to search for * @param {string} searchTerm - The term to search for
*/ */
performSearch(searchTerm) { performSearch(searchTerm) {
if (!this.roomManager.fileManager.currentFiles) return; // Fetch all files from the server for searching
fetch(`/api/rooms/${this.roomManager.roomId}/search?query=${encodeURIComponent(searchTerm)}`)
const filteredFiles = this.roomManager.fileManager.currentFiles.filter(file => { .then(response => response.json())
const searchLower = searchTerm.toLowerCase(); .then(files => {
return file.name.toLowerCase().includes(searchLower) || // Modify the file objects to show full paths in search results
(file.type && file.type.toLowerCase().includes(searchLower)); const modifiedFiles = files.map(file => ({
}); ...file,
displayName: this.isSearching ? file.full_path : file.name
this.roomManager.viewManager.renderFiles(filteredFiles); }));
this.roomManager.viewManager.renderFiles(modifiedFiles);
})
.catch(error => {
console.error('Error performing search:', error);
});
} }
/** /**

View File

@@ -255,6 +255,11 @@ export class ViewManager {
const size = isFolder ? '-' : this.formatFileSize(file.size); const size = isFolder ? '-' : this.formatFileSize(file.size);
const modified = new Date(file.modified).toLocaleString(); const modified = new Date(file.modified).toLocaleString();
// Create file name element
const name = document.createElement('span');
name.className = 'file-name';
name.textContent = file.displayName || file.name;
return ` return `
<tr data-index="${index}" class="file-row" style="cursor: pointer;" <tr data-index="${index}" class="file-row" style="cursor: pointer;"
onclick="window.roomManager.fileManager.updateSelection(${index}, event)" onclick="window.roomManager.fileManager.updateSelection(${index}, event)"
@@ -268,7 +273,7 @@ export class ViewManager {
<i class="fas ${icon}" style="font-size:1.5rem;color:${isFolder ? 'var(--primary-color)' : 'var(--secondary-color)'}"></i> <i class="fas ${icon}" style="font-size:1.5rem;color:${isFolder ? 'var(--primary-color)' : 'var(--secondary-color)'}"></i>
</td> </td>
<td> <td>
<span class="file-name">${file.name}</span> ${name.outerHTML}
</td> </td>
<td class="text-muted">${size}</td> <td class="text-muted">${size}</td>
<td class="text-muted">${modified}</td> <td class="text-muted">${modified}</td>
@@ -294,6 +299,11 @@ export class ViewManager {
const size = isFolder ? '-' : this.formatFileSize(file.size); const size = isFolder ? '-' : this.formatFileSize(file.size);
const modified = new Date(file.modified).toLocaleString(); const modified = new Date(file.modified).toLocaleString();
// Create file name element
const name = document.createElement('span');
name.className = 'file-name';
name.textContent = file.displayName || file.name;
return ` return `
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3"> <div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3">
<div class="card file-card h-100 border-0 shadow-sm position-relative" <div class="card file-card h-100 border-0 shadow-sm position-relative"
@@ -309,7 +319,7 @@ export class ViewManager {
<div class="mb-2"> <div class="mb-2">
<i class="fas ${icon}" style="font-size:2.5rem;color:${isFolder ? 'var(--primary-color)' : 'var(--secondary-color)'};"></i> <i class="fas ${icon}" style="font-size:2.5rem;color:${isFolder ? 'var(--primary-color)' : 'var(--secondary-color)'};"></i>
</div> </div>
<div class="fw-bold file-name-ellipsis mb-1" title="${file.name}">${file.name}</div> <div class="fw-bold file-name-ellipsis mb-1" title="${file.name}">${name.outerHTML}</div>
<div class="text-muted" style="font-size:0.85rem;">${modified}</div> <div class="text-muted" style="font-size:0.85rem;">${modified}</div>
<div class="text-muted mb-2" style="font-size:0.85rem;">${size}</div> <div class="text-muted mb-2" style="font-size:0.85rem;">${size}</div>
</div> </div>