400 lines
No EOL
20 KiB
HTML
400 lines
No EOL
20 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - Vim Docs{% endblock %}
|
|
|
|
{% block header_title %}Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<!-- Welcome banner -->
|
|
<div class="bg-gray-800 rounded-lg overflow-hidden shadow-lg">
|
|
<div class="p-6">
|
|
<div class="flex items-start">
|
|
<div class="flex-1">
|
|
<h1 class="text-2xl font-bold text-white mb-2">Welcome back, {{ current_user.username }}!</h1>
|
|
<p class="text-gray-400 mb-4">Manage your Vim and coding documentation with ease.</p>
|
|
<div class="flex space-x-3">
|
|
<a href="{{ url_for('main.new_document') }}" class="inline-flex items-center px-4 py-2 bg-primary text-black rounded-md hover:bg-primary-dark transition-colors">
|
|
<i class="mdi mdi-file-plus-outline mr-2"></i> New Document
|
|
</a>
|
|
<button id="category-btn" class="inline-flex items-center px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors">
|
|
<i class="mdi mdi-folder-plus-outline mr-2"></i> New Category
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="hidden md:block">
|
|
<i class="mdi mdi-vim text-primary text-9xl opacity-20"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent documents section -->
|
|
<div>
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-xl font-semibold text-white">Recent Documents</h2>
|
|
<a href="#" class="text-primary hover:text-primary-light flex items-center text-sm">
|
|
View All <i class="mdi mdi-chevron-right ml-1"></i>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{% if recent_docs %}
|
|
{% for doc in recent_docs %}
|
|
<div class="bg-gray-800 rounded-lg overflow-hidden shadow hover:shadow-lg transition-shadow">
|
|
<div class="p-5">
|
|
<div class="flex items-start justify-between">
|
|
<h3 class="text-white font-medium mb-2 truncate">
|
|
<a href="{{ url_for('main.view_document', doc_id=doc.id) }}" class="hover:text-primary transition-colors">
|
|
{{ doc.title }}
|
|
</a>
|
|
</h3>
|
|
<div class="dropdown relative ml-2">
|
|
<button class="icon-button p-1">
|
|
<i class="mdi mdi-dots-vertical"></i>
|
|
</button>
|
|
<div class="dropdown-menu hidden absolute right-0 mt-2 w-40 bg-gray-700 rounded-md shadow-lg z-10">
|
|
<a href="{{ url_for('main.edit_document', doc_id=doc.id) }}" class="block px-4 py-2 text-gray-300 hover:bg-gray-600 hover:text-white">
|
|
<i class="mdi mdi-pencil mr-2"></i> Edit
|
|
</a>
|
|
<a href="{{ url_for('main.export_document', doc_id=doc.id) }}" class="block px-4 py-2 text-gray-300 hover:bg-gray-600 hover:text-white">
|
|
<i class="mdi mdi-download mr-2"></i> Export
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-gray-400 text-sm mb-3 truncate">
|
|
{{ (doc.content[:100] + '...') if doc.content|length > 100 else doc.content }}
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between mt-4">
|
|
<div class="text-xs text-gray-500">
|
|
<i class="mdi mdi-calendar-outline mr-1"></i> {{ doc.updated_date.strftime('%b %d, %Y') }}
|
|
</div>
|
|
|
|
{% if doc.category %}
|
|
<div class="flex items-center text-xs text-gray-500">
|
|
<i class="mdi {{ doc.category.icon }} mr-1"></i> {{ doc.category.name }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if doc.tags %}
|
|
<div class="flex flex-wrap gap-1 mt-3">
|
|
{% for tag in doc.tags %}
|
|
<span class="text-xs px-2 py-1 bg-primary/20 text-primary rounded-full">{{ tag.name }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="col-span-full bg-gray-800/50 rounded-lg p-8 text-center">
|
|
<i class="mdi mdi-file-document-outline text-6xl text-gray-700 mb-3"></i>
|
|
<h3 class="text-lg text-gray-400 mb-3">No documents yet</h3>
|
|
<p class="text-gray-500 mb-4">Create your first document to get started</p>
|
|
<a href="{{ url_for('main.new_document') }}" class="inline-flex items-center px-4 py-2 bg-primary text-black rounded-md hover:bg-primary-dark transition-colors">
|
|
<i class="mdi mdi-plus mr-2"></i> Create Document
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories section -->
|
|
<div>
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-xl font-semibold text-white">My Categories</h2>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
{% if categories %}
|
|
{% for category in categories %}
|
|
<div class="bg-gray-800 rounded-lg overflow-hidden shadow hover:shadow-lg transition-all hover:-translate-y-1 duration-200">
|
|
<a href="{{ url_for('main.view_category', category_id=category.id) }}" class="block p-5">
|
|
<div class="flex items-center mb-3">
|
|
<div class="w-10 h-10 rounded-md bg-primary/20 flex items-center justify-center text-primary mr-3">
|
|
<i class="mdi {{ category.icon }} text-2xl"></i>
|
|
</div>
|
|
<h3 class="text-white font-medium truncate">{{ category.name }}</h3>
|
|
</div>
|
|
|
|
{% if category.description %}
|
|
<p class="text-gray-400 text-sm mb-4 line-clamp-2">{{ category.description }}</p>
|
|
{% endif %}
|
|
|
|
<div class="flex items-center justify-between text-xs text-gray-500">
|
|
<span>{{ category.documents.count() }} document{{ '' if category.documents.count() == 1 else 's' }}</span>
|
|
<span>{{ category.children.count() }} subcategor{{ 'y' if category.children.count() == 1 else 'ies' }}</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Add category card -->
|
|
<div id="add-category-card" class="bg-gray-800/50 border-2 border-dashed border-gray-700 rounded-lg overflow-hidden hover:border-primary/50 hover:bg-gray-800/80 transition-all cursor-pointer">
|
|
<div class="p-5 h-full flex flex-col items-center justify-center text-center">
|
|
<div class="w-12 h-12 rounded-full bg-gray-700/50 flex items-center justify-center mb-3">
|
|
<i class="mdi mdi-folder-plus-outline text-2xl text-gray-500"></i>
|
|
</div>
|
|
<h3 class="text-gray-400 font-medium mb-1">New Category</h3>
|
|
<p class="text-gray-500 text-sm">Create a new category to organize your docs</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="col-span-full bg-gray-800/50 rounded-lg p-8 text-center">
|
|
<i class="mdi mdi-folder-outline text-6xl text-gray-700 mb-3"></i>
|
|
<h3 class="text-lg text-gray-400 mb-3">No categories yet</h3>
|
|
<p class="text-gray-500 mb-4">Organize your documents by creating categories</p>
|
|
<button id="empty-add-category-btn" class="inline-flex items-center px-4 py-2 bg-primary text-black rounded-md hover:bg-primary-dark transition-colors">
|
|
<i class="mdi mdi-plus mr-2"></i> Create Category
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Modal -->
|
|
<div id="category-modal" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center hidden">
|
|
<div class="bg-gray-800 rounded-lg shadow-lg w-full max-w-md mx-4">
|
|
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
|
<h3 id="modal-title" class="text-lg font-medium text-white">Add Category</h3>
|
|
<button id="close-modal" class="text-gray-400 hover:text-white">
|
|
<i class="mdi mdi-close text-lg"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<form id="category-form" class="space-y-4">
|
|
<input type="hidden" id="category-id" value="">
|
|
|
|
<div>
|
|
<label for="category-name" class="block text-sm font-medium text-gray-400 mb-1">Name</label>
|
|
<input type="text" id="category-name" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="category-icon" class="block text-sm font-medium text-gray-400 mb-1">Icon</label>
|
|
<div class="flex items-center">
|
|
<input type="text" id="category-icon" value="mdi-folder-outline" class="flex-1 px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary">
|
|
<div class="ml-3 w-10 h-10 flex items-center justify-center text-primary bg-primary/10 rounded-md">
|
|
<i id="icon-preview" class="mdi mdi-folder-outline text-xl"></i>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 flex flex-wrap gap-2">
|
|
<button type="button" data-icon="mdi-folder-outline" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-folder-outline"></i>
|
|
</button>
|
|
<button type="button" data-icon="mdi-folder-text-outline" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-folder-text-outline"></i>
|
|
</button>
|
|
<button type="button" data-icon="mdi-code-braces" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-code-braces"></i>
|
|
</button>
|
|
<button type="button" data-icon="mdi-database" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-database"></i>
|
|
</button>
|
|
<button type="button" data-icon="mdi-web" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-web"></i>
|
|
</button>
|
|
<button type="button" data-icon="mdi-book-outline" class="icon-select w-8 h-8 flex items-center justify-center bg-gray-700 rounded-md text-primary hover:bg-gray-600">
|
|
<i class="mdi mdi-book-outline"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="category-description" class="block text-sm font-medium text-gray-400 mb-1">Description (Optional)</label>
|
|
<input type="text" id="category-description" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="category-parent" class="block text-sm font-medium text-gray-400 mb-1">Parent Category</label>
|
|
<select id="category-parent" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary">
|
|
<option value="">None</option>
|
|
{% for category in categories %}
|
|
<option value="{{ category.id }}">{{ category.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="pt-4 flex justify-end space-x-3">
|
|
<button type="button" id="cancel-btn" class="px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors">
|
|
Cancel
|
|
</button>
|
|
<button type="submit" class="px-4 py-2 bg-primary text-black rounded-md hover:bg-primary-dark transition-colors">
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Modal JavaScript -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log("DOM content loaded, initializing category modal");
|
|
|
|
// Category modal functionality
|
|
const modal = document.getElementById('category-modal');
|
|
const addCategoryBtn = document.getElementById('category-btn');
|
|
const addCategoryCard = document.getElementById('add-category-card');
|
|
const emptyAddBtn = document.getElementById('empty-add-category-btn');
|
|
const closeModalBtn = document.getElementById('close-modal');
|
|
const cancelBtn = document.getElementById('cancel-btn');
|
|
const form = document.getElementById('category-form');
|
|
const iconInput = document.getElementById('category-icon');
|
|
const iconPreview = document.getElementById('icon-preview');
|
|
const iconSelectBtns = document.querySelectorAll('.icon-select');
|
|
|
|
// Open modal function
|
|
function openModal() {
|
|
console.log("Opening category modal");
|
|
modal.classList.remove('hidden');
|
|
document.getElementById('category-name').focus();
|
|
}
|
|
|
|
// Close modal function
|
|
function closeModal() {
|
|
console.log("Closing category modal");
|
|
modal.classList.add('hidden');
|
|
form.reset();
|
|
document.getElementById('category-id').value = '';
|
|
iconPreview.className = 'mdi mdi-folder-outline text-xl';
|
|
}
|
|
|
|
// Update icon preview
|
|
if (iconInput) {
|
|
iconInput.addEventListener('input', function() {
|
|
iconPreview.className = 'mdi ' + this.value + ' text-xl';
|
|
});
|
|
}
|
|
|
|
// Icon selection
|
|
iconSelectBtns.forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const icon = this.getAttribute('data-icon');
|
|
iconInput.value = icon;
|
|
iconPreview.className = 'mdi ' + icon + ' text-xl';
|
|
|
|
// Highlight selected icon
|
|
iconSelectBtns.forEach(b => b.classList.remove('ring-2', 'ring-primary'));
|
|
this.classList.add('ring-2', 'ring-primary');
|
|
});
|
|
});
|
|
|
|
// Event listeners for opening the modal
|
|
console.log("Setting up event listeners");
|
|
if (addCategoryBtn) {
|
|
console.log("Found category-btn, adding click handler");
|
|
addCategoryBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
openModal();
|
|
});
|
|
}
|
|
|
|
if (addCategoryCard) {
|
|
console.log("Found add-category-card, adding click handler");
|
|
addCategoryCard.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
openModal();
|
|
});
|
|
}
|
|
|
|
if (emptyAddBtn) {
|
|
console.log("Found empty-add-category-btn, adding click handler");
|
|
emptyAddBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
openModal();
|
|
});
|
|
}
|
|
|
|
// Event listeners for closing the modal
|
|
if (closeModalBtn) {
|
|
closeModalBtn.addEventListener('click', closeModal);
|
|
}
|
|
|
|
if (cancelBtn) {
|
|
cancelBtn.addEventListener('click', closeModal);
|
|
}
|
|
|
|
// Add click outside to close
|
|
window.addEventListener('click', function(e) {
|
|
if (e.target === modal) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
// Form submission
|
|
if (form) {
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
console.log("Category form submitted");
|
|
|
|
const categoryData = {
|
|
name: document.getElementById('category-name').value,
|
|
icon: document.getElementById('category-icon').value,
|
|
description: document.getElementById('category-description').value,
|
|
parent_id: document.getElementById('category-parent').value || null
|
|
};
|
|
|
|
const categoryId = document.getElementById('category-id').value;
|
|
if (categoryId) {
|
|
categoryData.id = categoryId;
|
|
}
|
|
|
|
console.log("Sending category data:", categoryData);
|
|
|
|
fetch('/api/category', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': '{{ csrf_token() }}'
|
|
},
|
|
body: JSON.stringify(categoryData)
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Server responded with an error: ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
console.log("Category saved successfully:", data);
|
|
// Reload the page to show the new category
|
|
window.location.reload();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error saving category:', error);
|
|
alert('Error saving category: ' + error.message);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Dropdown functionality for document cards
|
|
document.querySelectorAll('.dropdown button').forEach(btn => {
|
|
btn.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
const menu = this.nextElementSibling;
|
|
menu.classList.toggle('hidden');
|
|
|
|
// Close other open dropdowns
|
|
document.querySelectorAll('.dropdown-menu:not(.hidden)').forEach(m => {
|
|
if (m !== menu) m.classList.add('hidden');
|
|
});
|
|
});
|
|
});
|
|
|
|
// Close dropdowns when clicking outside
|
|
document.addEventListener('click', function() {
|
|
document.querySelectorAll('.dropdown-menu:not(.hidden)').forEach(menu => {
|
|
menu.classList.add('hidden');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |