wip
This commit is contained in:
parent
f5c8e9ee23
commit
3a16f266da
15 changed files with 511 additions and 169 deletions
|
@ -56,49 +56,53 @@
|
|||
<div class="flex min-h-screen {% if request.cookies.get('sidebar_collapsed') == 'true' %}sidebar-hidden{% endif %}">
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-gray-800 h-screen flex-shrink-0 fixed left-0 top-0 z-10 overflow-y-auto transition-all ease-in-out duration-300">
|
||||
<div class="p-4 border-b border-gray-700 flex items-center">
|
||||
<div class="p-4 border-b border-gray-700/30 flex items-center">
|
||||
<h1 class="text-xl font-semibold text-white flex items-center">
|
||||
<i class="mdi mdi-vim text-primary text-2xl mr-2"></i> Vim Docs
|
||||
</h1>
|
||||
</div>
|
||||
<nav class="py-4">
|
||||
<nav class="py-2">
|
||||
<ul class="list-none">
|
||||
<li class="my-1">
|
||||
<a href="{{ url_for('main.index') }}" class="flex items-center px-4 py-2 text-gray-300 hover:text-primary hover:bg-gray-700 rounded-md transition-all {{ 'bg-primary/10 text-primary' if request.endpoint == 'main.index' }}">
|
||||
<li>
|
||||
<a href="{{ url_for('main.index') }}" class="flex items-center px-4 py-2 text-gray-300 hover:text-primary hover:bg-gray-700/50 rounded-md transition-all {{ 'bg-primary/10 text-primary' if request.endpoint == 'main.index' }}">
|
||||
<i class="mdi mdi-home mr-3"></i> Home
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="my-4">
|
||||
<div class="block font-medium text-gray-400 px-4 py-2 flex items-center">
|
||||
<i class="mdi mdi-folder-multiple-outline mr-3"></i> Categories
|
||||
</div>
|
||||
<ul class="ml-3 pl-3 border-l border-gray-700 my-1" id="category-tree">
|
||||
<li class="mt-3">
|
||||
<a href="#" id="root-category-link" class="flex items-center justify-between px-4 py-2 text-gray-300 hover:text-primary hover:bg-gray-700/50 rounded-md transition-all">
|
||||
<div class="flex items-center">
|
||||
<i class="mdi mdi-folder-outline mr-3"></i>
|
||||
<span>Files & Categories</span>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-down text-sm"></i>
|
||||
</a>
|
||||
<ul class="ml-3 pt-1" id="category-tree">
|
||||
<!-- Categories will be loaded here via JS -->
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="my-4">
|
||||
<div class="block font-medium text-gray-400 px-4 py-2 flex items-center">
|
||||
<li class="mt-3">
|
||||
<div class="block font-medium text-gray-400 px-4 py-1 flex items-center">
|
||||
<i class="mdi mdi-file-document-multiple-outline mr-3"></i> Documents
|
||||
</div>
|
||||
<ul class="ml-3 pl-3 border-l border-gray-700 my-1">
|
||||
<ul class="ml-3 pt-1">
|
||||
<li>
|
||||
<a href="{{ url_for('main.new_document') }}" class="flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors {{ 'text-primary' if request.endpoint == 'main.new_document' }}">
|
||||
<a href="{{ url_for('main.new_document') }}" class="flex items-center py-1 px-2 text-gray-400 hover:text-primary hover:bg-gray-700/30 rounded transition-colors {{ 'text-primary' if request.endpoint == 'main.new_document' }}">
|
||||
<i class="mdi mdi-plus-circle-outline mr-2 text-sm"></i> New Document
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('main.index') }}?view=recent" class="flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors">
|
||||
<a href="{{ url_for('main.index') }}?view=recent" class="flex items-center py-1 px-2 text-gray-400 hover:text-primary hover:bg-gray-700/30 rounded transition-colors">
|
||||
<i class="mdi mdi-clock-outline mr-2 text-sm"></i> Recent Documents
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="my-1">
|
||||
<a href="{{ url_for('main.new_document') }}" class="flex items-center px-4 py-2 bg-primary text-black hover:bg-primary-dark rounded-md transition-all">
|
||||
<i class="mdi mdi-plus-circle mr-3"></i> New Document
|
||||
<li class="mt-4 mb-2 px-3">
|
||||
<a href="{{ url_for('main.new_document') }}" class="flex items-center justify-center px-4 py-2 bg-primary text-black hover:bg-primary-dark rounded-md transition-all">
|
||||
<i class="mdi mdi-plus-circle mr-2"></i> New Document
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -188,6 +192,55 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Root category toggle
|
||||
const rootCategoryLink = document.getElementById('root-category-link');
|
||||
const categoryTree = document.getElementById('category-tree');
|
||||
|
||||
if (rootCategoryLink && categoryTree) {
|
||||
// Initialize - expand by default
|
||||
rootCategoryLink.querySelector('i.mdi-chevron-down').classList.add('rotate-180');
|
||||
|
||||
rootCategoryLink.addEventListener('click', function(e) {
|
||||
// Only handle the toggle if clicking on the chevron icon
|
||||
if (e.target.classList.contains('mdi-chevron-down') || e.target.parentElement.classList.contains('mdi-chevron-down')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Toggle category tree visibility
|
||||
if (categoryTree.classList.contains('hidden')) {
|
||||
categoryTree.classList.remove('hidden');
|
||||
rootCategoryLink.querySelector('i.mdi-chevron-down').classList.add('rotate-180');
|
||||
} else {
|
||||
categoryTree.classList.add('hidden');
|
||||
rootCategoryLink.querySelector('i.mdi-chevron-down').classList.remove('rotate-180');
|
||||
}
|
||||
|
||||
// Save preference in localStorage
|
||||
localStorage.setItem('rootCategoryExpanded', !categoryTree.classList.contains('hidden'));
|
||||
} else {
|
||||
// If not clicking on the chevron, we need to load the root category
|
||||
fetch('/api/categories')
|
||||
.then(response => response.json())
|
||||
.then(categories => {
|
||||
const rootCategory = categories.find(c => c.is_root);
|
||||
if (rootCategory) {
|
||||
window.location.href = `/category/${rootCategory.id}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching root category:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Load saved preference
|
||||
const rootExpanded = localStorage.getItem('rootCategoryExpanded') !== 'false';
|
||||
if (!rootExpanded) {
|
||||
categoryTree.classList.add('hidden');
|
||||
rootCategoryLink.querySelector('i.mdi-chevron-down').classList.remove('rotate-180');
|
||||
}
|
||||
}
|
||||
|
||||
// Load categories
|
||||
loadCategories();
|
||||
|
||||
|
@ -205,20 +258,155 @@
|
|||
categoryTree.innerHTML = ''; // Clear existing items
|
||||
|
||||
if (categories.length === 0) {
|
||||
categoryTree.innerHTML = '<li class="px-4 py-2 text-gray-500">No categories found</li>';
|
||||
categoryTree.innerHTML = '<li class="px-4 py-2 text-gray-500">No items found</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
categories.forEach(category => {
|
||||
categoryTree.appendChild(createCategoryItem(category));
|
||||
});
|
||||
// First add documents without categories (directly in root)
|
||||
const rootCategory = categories.find(c => c.is_root);
|
||||
if (rootCategory && rootCategory.documents && rootCategory.documents.length > 0) {
|
||||
// Create a section header for root documents
|
||||
const docHeader = document.createElement('div');
|
||||
docHeader.className = 'text-xs uppercase text-gray-500 font-medium px-2 py-1 mt-2';
|
||||
docHeader.textContent = 'Files';
|
||||
categoryTree.appendChild(docHeader);
|
||||
|
||||
// Create a container for documents
|
||||
const docsContainer = document.createElement('div');
|
||||
docsContainer.className = 'mb-2';
|
||||
categoryTree.appendChild(docsContainer);
|
||||
|
||||
const documentsUl = document.createElement('ul');
|
||||
documentsUl.className = 'py-1 space-y-0.5';
|
||||
docsContainer.appendChild(documentsUl);
|
||||
|
||||
rootCategory.documents.forEach(docId => {
|
||||
// Fetch document details and add to the tree
|
||||
fetch(`/api/document/${docId}`)
|
||||
.then(response => response.json())
|
||||
.then(doc => {
|
||||
const docLi = document.createElement('li');
|
||||
docLi.className = 'document-item relative group';
|
||||
|
||||
const docLink = document.createElement('a');
|
||||
docLink.href = `/document/${doc.id}`;
|
||||
docLink.className = 'flex items-center py-1 px-2 text-gray-400 hover:text-primary hover:bg-gray-700/30 rounded transition-colors truncate';
|
||||
docLink.innerHTML = `<i class="mdi mdi-file-document-outline mr-2 text-sm"></i> <span class="truncate">${doc.title}</span>`;
|
||||
|
||||
// Add drag functionality
|
||||
docLink.draggable = true;
|
||||
docLink.dataset.docId = doc.id;
|
||||
docLink.addEventListener('dragstart', function(e) {
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify({
|
||||
type: 'document',
|
||||
id: doc.id,
|
||||
title: doc.title
|
||||
}));
|
||||
docLi.classList.add('dragging');
|
||||
});
|
||||
|
||||
docLink.addEventListener('dragend', function() {
|
||||
docLi.classList.remove('dragging');
|
||||
});
|
||||
|
||||
// Add document actions
|
||||
const docActions = createDocumentActions(doc, docLi, rootCategory.id);
|
||||
|
||||
docLi.appendChild(docLink);
|
||||
docLi.appendChild(docActions);
|
||||
documentsUl.appendChild(docLi);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error fetching document ${docId}:`, error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If there are any non-root categories, add a section header
|
||||
const nonRootCategories = categories.filter(c => !c.is_root);
|
||||
if (nonRootCategories.length > 0) {
|
||||
const catHeader = document.createElement('div');
|
||||
catHeader.className = 'text-xs uppercase text-gray-500 font-medium px-2 py-1 mt-3';
|
||||
catHeader.textContent = 'Categories';
|
||||
categoryTree.appendChild(catHeader);
|
||||
|
||||
// Then add all non-root categories
|
||||
nonRootCategories.forEach(category => {
|
||||
categoryTree.appendChild(createCategoryItem(category));
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading categories:', error);
|
||||
categoryTree.innerHTML = '<li class="px-4 py-2 text-red-500">Error loading categories</li>';
|
||||
categoryTree.innerHTML = '<li class="px-4 py-2 text-red-500">Error loading items</li>';
|
||||
});
|
||||
}
|
||||
|
||||
function createDocumentActions(doc, docLi, categoryId) {
|
||||
const docActions = document.createElement('div');
|
||||
docActions.className = 'actions absolute right-0 hidden group-hover:flex items-center bg-gray-800/90 px-1 rounded-sm';
|
||||
|
||||
// Edit document button
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'p-1 text-gray-500 hover:text-primary rounded transition-colors';
|
||||
editBtn.title = 'Edit document';
|
||||
editBtn.innerHTML = '<i class="mdi mdi-pencil-outline text-sm"></i>';
|
||||
editBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href = `/document/${doc.id}/edit`;
|
||||
});
|
||||
docActions.appendChild(editBtn);
|
||||
|
||||
// Delete document button
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'p-1 text-gray-500 hover:text-red-500 rounded transition-colors';
|
||||
deleteBtn.title = 'Delete document';
|
||||
deleteBtn.innerHTML = '<i class="mdi mdi-delete-outline text-sm"></i>';
|
||||
deleteBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Show confirmation dialog
|
||||
if (confirm(`Are you sure you want to delete "${doc.title}"? This cannot be undone.`)) {
|
||||
// Send delete request
|
||||
fetch(`/api/document/${doc.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Remove the document from the DOM
|
||||
docLi.remove();
|
||||
|
||||
// Show notification
|
||||
showNotification('Document deleted successfully');
|
||||
|
||||
// If we're on the document's page, redirect to the category or home
|
||||
const currentPath = window.location.pathname;
|
||||
if (currentPath === `/document/${doc.id}` || currentPath === `/document/${doc.id}/edit`) {
|
||||
window.location.href = categoryId ? `/category/${categoryId}` : '/';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('Error deleting document', 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
docActions.appendChild(deleteBtn);
|
||||
|
||||
return docActions;
|
||||
}
|
||||
|
||||
function createCategoryItem(category) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'category-item my-1';
|
||||
|
@ -248,7 +436,7 @@
|
|||
// Create the link to view the category
|
||||
const a = document.createElement('a');
|
||||
a.href = `/category/${category.id}`;
|
||||
let categoryClass = 'flex-grow flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors overflow-hidden';
|
||||
let categoryClass = 'flex-grow flex items-center py-1 px-2 text-gray-400 hover:text-primary hover:bg-gray-700/30 rounded transition-colors overflow-hidden';
|
||||
|
||||
// Special styling for root
|
||||
if (category.is_root) {
|
||||
|
@ -264,7 +452,7 @@
|
|||
|
||||
// Create actions container that appears on hover
|
||||
const actionsContainer = document.createElement('div');
|
||||
actionsContainer.className = 'actions absolute right-0 hidden group-hover:flex items-center bg-gray-800 px-1';
|
||||
actionsContainer.className = 'actions absolute right-0 hidden group-hover:flex items-center bg-gray-800/90 px-1 rounded-sm';
|
||||
categoryContainer.appendChild(actionsContainer);
|
||||
|
||||
// Add document button - consistent across all views
|
||||
|
@ -305,7 +493,7 @@
|
|||
|
||||
// Create the child container for documents and subcategories
|
||||
const childrenContainer = document.createElement('div');
|
||||
childrenContainer.className = 'ml-2 pl-2 border-l border-gray-700 mt-1 mb-1 overflow-hidden transition-all duration-300';
|
||||
childrenContainer.className = 'ml-4 mt-1 mb-1 overflow-hidden transition-all duration-300';
|
||||
childrenContainer.style.display = 'none'; // Initially collapsed
|
||||
li.appendChild(childrenContainer);
|
||||
|
||||
|
@ -357,7 +545,7 @@
|
|||
// Add documents first
|
||||
if (hasDocuments) {
|
||||
const documentsUl = document.createElement('ul');
|
||||
documentsUl.className = 'py-1 space-y-1';
|
||||
documentsUl.className = 'py-1 space-y-0.5';
|
||||
childrenContainer.appendChild(documentsUl);
|
||||
|
||||
// Sort documents by name
|
||||
|
@ -374,7 +562,7 @@
|
|||
|
||||
const docLink = document.createElement('a');
|
||||
docLink.href = `/document/${doc.id}`;
|
||||
docLink.className = 'flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors truncate';
|
||||
docLink.className = 'flex items-center py-1 px-2 text-gray-400 hover:text-primary hover:bg-gray-700/30 rounded transition-colors truncate';
|
||||
docLink.innerHTML = `<i class="mdi mdi-file-document-outline mr-2 text-sm"></i> <span class="truncate">${doc.title}</span>`;
|
||||
|
||||
// Add drag functionality
|
||||
|
@ -394,66 +582,7 @@
|
|||
});
|
||||
|
||||
// Add actions for documents
|
||||
const docActions = document.createElement('div');
|
||||
docActions.className = 'actions absolute right-0 hidden group-hover:flex items-center bg-gray-800 px-1';
|
||||
|
||||
// Edit document button
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'p-1 text-gray-500 hover:text-primary rounded transition-colors';
|
||||
editBtn.title = 'Edit document';
|
||||
editBtn.innerHTML = '<i class="mdi mdi-pencil-outline text-sm"></i>';
|
||||
editBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href = `/document/${doc.id}/edit`;
|
||||
});
|
||||
docActions.appendChild(editBtn);
|
||||
|
||||
// Delete document button
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'p-1 text-gray-500 hover:text-red-500 rounded transition-colors';
|
||||
deleteBtn.title = 'Delete document';
|
||||
deleteBtn.innerHTML = '<i class="mdi mdi-delete-outline text-sm"></i>';
|
||||
deleteBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Show confirmation dialog
|
||||
if (confirm(`Are you sure you want to delete "${doc.title}"? This cannot be undone.`)) {
|
||||
// Send delete request
|
||||
fetch(`/api/document/${doc.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Remove the document from the DOM
|
||||
docLi.remove();
|
||||
|
||||
// Show notification
|
||||
showNotification('Document deleted successfully');
|
||||
|
||||
// If we're on the document's page, redirect to the category or home
|
||||
const currentPath = window.location.pathname;
|
||||
if (currentPath === `/document/${doc.id}` || currentPath === `/document/${doc.id}/edit`) {
|
||||
window.location.href = `/category/${category.id}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('Error deleting document', 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
docActions.appendChild(deleteBtn);
|
||||
const docActions = createDocumentActions(doc, docLi, category.id);
|
||||
|
||||
docLi.appendChild(docLink);
|
||||
docLi.appendChild(docActions);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue