Files
gobsidian/web/templates/folder.html
2025-08-25 21:19:15 +01:00

275 lines
11 KiB
HTML

{{define "folder"}}
{{template "base" .}}
{{end}}
{{define "folder_content"}}
<div class="p-6">
<!-- Header with upload button -->
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-white mb-2">
{{if .folder_path}}
{{.folder_path}}
{{else}}
Welcome to {{.app_name}}
{{end}}
</h1>
<p class="text-gray-400">
{{if .folder_contents}}
{{len .folder_contents}} items
{{else}}
No items found
{{end}}
</p>
</div>
<div class="flex items-center space-x-3">
<button id="upload-btn" class="btn-primary">
<i class="fas fa-upload mr-2"></i>Upload File
</button>
<a href="/editor/create?folder={{.folder_path}}" class="btn-secondary">
<i class="fas fa-plus mr-2"></i>New Note
</a>
</div>
</div>
<!-- Upload Area (hidden by default) -->
<div id="upload-area" class="upload-area mb-6 hidden">
<div class="flex flex-col items-center">
<i class="fas fa-cloud-upload text-4xl text-gray-500 mb-4"></i>
<p class="text-gray-400 mb-2">Drag and drop files here or click to select</p>
<input type="file" id="file-input" multiple class="hidden">
<button id="select-files" class="btn-secondary">Select Files</button>
</div>
<div id="upload-progress" class="mt-4 hidden">
<div class="w-full bg-gray-700 rounded-full h-2">
<div id="progress-bar" class="bg-blue-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<p id="upload-status" class="text-sm text-gray-400 mt-2"></p>
</div>
</div>
<!-- Content Grid -->
<div class="grid gap-4">
{{if .folder_contents}}
{{range .folder_contents}}
<div class="bg-gray-800 rounded-lg p-4 hover:bg-gray-700 transition-colors duration-200 cursor-pointer item-card"
data-path="{{.Path}}" data-type="{{.Type}}">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<span class="text-2xl">{{fileTypeIcon .Type}}</span>
<div>
<h3 class="font-medium text-white">{{.DisplayName}}</h3>
<p class="text-sm text-gray-400">
{{if eq .Type "dir"}}
Folder
{{else}}
{{formatSize .Size}} • {{formatTime .ModTime}}
{{end}}
</p>
</div>
</div>
<div class="flex items-center space-x-2">
{{if eq .Type "md"}}
<a href="/editor/edit/{{.Path}}" class="text-blue-400 hover:text-blue-300 p-2" title="Edit">
<i class="fas fa-edit"></i>
</a>
{{end}}
{{if eq .Type "text"}}
<a href="/editor/edit_text/{{.Path}}" class="text-blue-400 hover:text-blue-300 p-2" title="Edit">
<i class="fas fa-edit"></i>
</a>
{{end}}
{{if eq .Type "image"}}
<a href="/serve_attached_image/{{.Path}}" target="_blank" class="text-yellow-400 hover:text-yellow-300 p-2" title="View">
<i class="fas fa-eye"></i>
</a>
{{end}}
{{if ne .Type "dir"}}
<a href="/download/{{.Path}}" class="text-green-400 hover:text-green-300 p-2" title="Download">
<i class="fas fa-download"></i>
</a>
{{end}}
<button class="text-red-400 hover:text-red-300 p-2 delete-btn" data-path="{{.Path}}" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
{{end}}
{{else}}
<div class="text-center py-12">
<i class="fas fa-folder-open text-6xl text-gray-600 mb-4"></i>
<p class="text-xl text-gray-400 mb-2">This folder is empty</p>
<p class="text-gray-500">Create a new note or upload files to get started</p>
</div>
{{end}}
</div>
</div>
<!-- Delete Confirmation Modal -->
<div id="delete-modal" class="modal-overlay hidden">
<div class="modal-content">
<h3 class="text-lg font-medium text-white mb-4">Confirm Delete</h3>
<p class="text-gray-300 mb-6">Are you sure you want to delete this item? This action cannot be undone.</p>
<div class="flex justify-end space-x-3">
<button id="cancel-delete" class="btn-secondary">Cancel</button>
<button id="confirm-delete" class="btn-danger">Delete</button>
</div>
</div>
</div>
{{end}}
{{define "folder_scripts"}}
<script>
let uploadArea = document.getElementById('upload-area');
let fileInput = document.getElementById('file-input');
let uploadBtn = document.getElementById('upload-btn');
let selectFilesBtn = document.getElementById('select-files');
let uploadProgress = document.getElementById('upload-progress');
let progressBar = document.getElementById('progress-bar');
let uploadStatus = document.getElementById('upload-status');
let deleteModal = document.getElementById('delete-modal');
let deleteTarget = null;
// Toggle upload area
uploadBtn.addEventListener('click', function() {
uploadArea.classList.toggle('hidden');
});
// File selection
selectFilesBtn.addEventListener('click', function() {
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
uploadFiles(this.files);
}
});
// Drag and drop
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
this.classList.remove('dragover');
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
uploadFiles(e.dataTransfer.files);
}
});
// Upload files function
function uploadFiles(files) {
uploadProgress.classList.remove('hidden');
progressBar.style.width = '0%';
uploadStatus.textContent = 'Preparing upload...';
const formData = new FormData();
formData.append('path', '{{.folder_path}}');
for (let file of files) {
formData.append('file', file);
}
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
const csrf = m && m[1] ? decodeURIComponent(m[1]) : '';
fetch('/editor/upload', {
method: 'POST',
headers: csrf ? { 'X-CSRF-Token': csrf } : {},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
progressBar.style.width = '100%';
uploadStatus.textContent = 'Upload complete!';
showNotification('Files uploaded successfully', 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
throw new Error(data.error || 'Upload failed');
}
})
.catch(error => {
uploadStatus.textContent = 'Upload failed: ' + error.message;
showNotification('Upload failed: ' + error.message, 'error');
});
}
// Item click handlers
document.addEventListener('click', function(e) {
const itemCard = e.target.closest('.item-card');
if (itemCard && !e.target.closest('a') && !e.target.closest('button')) {
const path = itemCard.dataset.path;
const type = itemCard.dataset.type;
if (type === 'dir') {
window.location.href = '/folder/' + path;
} else if (type === 'md') {
window.location.href = '/note/' + path;
} else if (type === 'image') {
window.open('/serve_attached_image/' + path, '_blank');
} else {
window.location.href = '/view_text/' + path;
}
}
});
// Delete functionality
document.addEventListener('click', function(e) {
if (e.target.closest('.delete-btn')) {
e.stopPropagation();
deleteTarget = e.target.closest('.delete-btn').dataset.path;
deleteModal.classList.remove('hidden');
}
});
document.getElementById('cancel-delete').addEventListener('click', function() {
deleteModal.classList.add('hidden');
deleteTarget = null;
});
document.getElementById('confirm-delete').addEventListener('click', function() {
if (deleteTarget) {
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
const csrf = m && m[1] ? decodeURIComponent(m[1]) : '';
fetch('/editor/delete/' + deleteTarget, {
method: 'DELETE',
headers: csrf ? { 'X-CSRF-Token': csrf } : {}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('Item deleted successfully', 'success');
window.location.reload();
} else {
throw new Error(data.error || 'Delete failed');
}
})
.catch(error => {
showNotification('Delete failed: ' + error.message, 'error');
});
}
deleteModal.classList.add('hidden');
deleteTarget = null;
});
// Close modal when clicking outside
deleteModal.addEventListener('click', function(e) {
if (e.target === this) {
this.classList.add('hidden');
deleteTarget = null;
}
});
</script>
{{end}}