removed persistant data

This commit is contained in:
pika 2025-04-14 22:37:25 +02:00
parent 345a801c40
commit 627f805377
10 changed files with 412 additions and 29 deletions

View file

@ -215,12 +215,80 @@
function createCategoryItem(category) {
const li = document.createElement('li');
// Create the category item container with flexbox to place the + icon
const categoryContainer = document.createElement('div');
categoryContainer.className = 'flex items-center justify-between group';
li.appendChild(categoryContainer);
// Create the link to view the category
const a = document.createElement('a');
a.href = `/category/${category.id}`;
a.className = 'flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors';
a.innerHTML = `<i class="mdi ${category.icon} mr-2 text-sm"></i> ${category.name}`;
li.appendChild(a);
let categoryClass = 'flex-grow flex items-center py-1 px-2 text-gray-400 hover:text-primary rounded transition-colors';
// Special styling for root
if (category.is_root) {
categoryClass += ' font-semibold text-primary';
}
a.className = categoryClass;
// Special icon for root
const iconClass = category.is_root ? 'mdi-folder-root' : category.icon;
a.innerHTML = `<i class="mdi ${iconClass} mr-2 text-sm"></i> ${category.name}`;
categoryContainer.appendChild(a);
// Create the dropdown menu container
const dropdownContainer = document.createElement('div');
dropdownContainer.className = 'relative';
categoryContainer.appendChild(dropdownContainer);
// Create the plus button
const plusButton = document.createElement('button');
plusButton.className = 'ml-1 p-1 text-gray-500 hover:text-primary rounded-full opacity-0 group-hover:opacity-100 transition-opacity';
plusButton.innerHTML = '<i class="mdi mdi-plus text-sm"></i>';
dropdownContainer.appendChild(plusButton);
// Create dropdown menu
const dropdown = document.createElement('div');
dropdown.className = 'absolute right-0 top-full mt-1 py-1 bg-gray-800 border border-gray-700 rounded-md shadow-lg z-20 hidden w-48';
dropdownContainer.appendChild(dropdown);
// Add menu items
const newSubcategory = document.createElement('a');
newSubcategory.href = `/category/new?parent_id=${category.id}`;
newSubcategory.className = 'block px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 hover:text-white w-full text-left';
newSubcategory.innerHTML = '<i class="mdi mdi-folder-plus-outline mr-2"></i> New Subcategory';
dropdown.appendChild(newSubcategory);
const newDocument = document.createElement('a');
newDocument.href = `/document/new?category=${category.id}`;
newDocument.className = 'block px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 hover:text-white w-full text-left';
newDocument.innerHTML = '<i class="mdi mdi-file-plus-outline mr-2"></i> New Document';
dropdown.appendChild(newDocument);
// Toggle dropdown
plusButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
dropdown.classList.toggle('hidden');
// Close other open dropdowns
document.querySelectorAll('.category-dropdown:not(.hidden)').forEach(el => {
if (el !== dropdown) el.classList.add('hidden');
});
});
// Add class for easy reference
dropdown.classList.add('category-dropdown');
// Add click handler to close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!plusButton.contains(e.target) && !dropdown.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
// Add children
if (category.children && category.children.length > 0) {
const childrenUl = document.createElement('ul');
childrenUl.className = 'ml-2 pl-2 border-l border-gray-700 my-1';

View file

@ -0,0 +1,193 @@
{% extends "base.html" %}
{% block title %}{% if category %}Edit Category{% else %}New Category{% endif %} - Vim Docs{% endblock %}
{% block header_title %}{% if category %}Edit Category{% else %}New Category{% endif %}{% endblock %}
{% block header_actions %}
<button id="save-category" 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-content-save mr-2"></i> Save
</button>
{% endblock %}
{% block content %}
<div class="max-w-2xl mx-auto">
<div class="bg-gray-800 rounded-lg p-6 shadow-lg">
<div class="grid grid-cols-1 gap-6">
<div>
<label for="category-name" class="block text-sm font-medium text-gray-400 mb-1">Category Name *</label>
<input type="text" id="category-name" value="{% if category %}{{ category.name }}{% endif %}"
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 (Material Design Icon)</label>
<input type="text" id="category-icon" value="{% if category %}{{ category.icon }}{% else %}mdi-folder-outline{% endif %}"
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">
<p class="text-sm text-gray-500 mt-1">Example: mdi-folder-outline, mdi-code-tags, mdi-book-open-page-variant</p>
</div>
<div>
<label for="category-description" class="block text-sm font-medium text-gray-400 mb-1">Description</label>
<textarea id="category-description" rows="3"
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">{% if category %}{{ category.description }}{% endif %}</textarea>
</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 (Root Category)</option>
<!-- Options will be populated with JavaScript -->
</select>
</div>
</div>
</div>
</div>
<!-- Save notification -->
<div id="save-notification" class="fixed bottom-4 right-4 bg-green-500/90 text-white px-4 py-2 rounded-md shadow-lg transform translate-y-16 opacity-0 transition-all duration-300 flex items-center">
<i class="mdi mdi-check-circle mr-2"></i>
<span>Category saved successfully!</span>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const saveButton = document.getElementById('save-category');
const nameInput = document.getElementById('category-name');
const iconInput = document.getElementById('category-icon');
const descriptionInput = document.getElementById('category-description');
const parentSelect = document.getElementById('category-parent');
const notification = document.getElementById('save-notification');
// Load all categories for parent selection
loadCategoryOptions();
// Set default parent if specified
{% if parent %}
const parentId = {{ parent.id }};
{% endif %}
function loadCategoryOptions() {
fetch('/api/categories')
.then(response => response.json())
.then(categories => {
// Find the root category
const rootCategory = categories.find(c => c.name === 'root');
// Add options recursively
function addCategoryOptions(categories, depth = 0) {
categories.forEach(category => {
// Skip the category being edited to avoid circular references
{% if category %}
if (category.id === {{ category.id }}) return;
{% endif %}
const option = document.createElement('option');
option.value = category.id;
// Add indentation using spaces for hierarchical display
const indent = '\u00A0\u00A0\u00A0\u00A0'.repeat(depth);
option.textContent = indent + (depth > 0 ? '└─ ' : '') + category.name;
// Select option logic:
// 1. If we have a parent specified, select that parent
// 2. If we're editing an existing category, select its current parent
// 3. If creating a new category, select root by default
let selectThisOption = false;
{% if parent %}
if (category.id === {{ parent.id }}) {
selectThisOption = true;
}
{% elif category and category.parent_id %}
if (category.id === {{ category.parent_id }}) {
selectThisOption = true;
}
{% else %}
// If no parent specified and creating new category, default to root
if (category.is_root && !rootSelected) {
selectThisOption = true;
rootSelected = true;
}
{% endif %}
option.selected = selectThisOption;
parentSelect.appendChild(option);
if (category.children && category.children.length > 0) {
addCategoryOptions(category.children, depth + 1);
}
});
}
// Initialize flag to track if root has been selected
let rootSelected = false;
// Start from root categories
addCategoryOptions(categories);
})
.catch(error => {
console.error('Error loading categories:', error);
});
}
saveButton.addEventListener('click', function() {
const categoryData = {
name: nameInput.value.trim(),
icon: iconInput.value.trim() || 'mdi-folder-outline',
description: descriptionInput.value.trim(),
parent_id: parentSelect.value || null
};
// Validation
if (!categoryData.name) {
alert('Please enter a category name');
nameInput.focus();
return;
}
{% if category %}
categoryData.id = {{ category.id }};
{% endif %}
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);
// Show save notification
notification.classList.remove('translate-y-16', 'opacity-0');
// Hide notification after 3 seconds
setTimeout(() => {
notification.classList.add('translate-y-16', 'opacity-0');
}, 3000);
// Redirect to category view
setTimeout(() => {
window.location.href = `/category/${data.id}`;
}, 1000);
})
.catch(error => {
console.error('Error saving category:', error);
alert('Error saving category: ' + error.message);
});
});
});
</script>
{% endblock %}

View file

@ -178,17 +178,7 @@
<div>
<label for="doc-category" class="block text-sm font-medium text-gray-400 mb-1">Category</label>
<select id="doc-category" 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">
{% for category in categories %}
{% if category.is_root %}
<option value="{{ category.id }}" {% if (document and document.category_id == category.id) or (not document and (not preselected_category_id or preselected_category_id|int == category.id|int)) %}selected{% endif %}>
{{ category.name }} (Home)
</option>
{% else %}
<option value="{{ category.id }}" {% if (document and document.category_id == category.id) or (not document and preselected_category_id and preselected_category_id|int == category.id|int) %}selected{% endif %}>
{{ category.name }}
</option>
{% endif %}
{% endfor %}
<!-- Categories will be populated via JavaScript -->
</select>
</div>
@ -315,9 +305,60 @@
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Populate categories dropdown with hierarchical structure
populateCategoriesDropdown();
// Initial preview
updatePreview();
// Function to create hierarchical category dropdown
function populateCategoriesDropdown() {
const categorySelect = document.getElementById('doc-category');
const categories = {{ categories|tojson|safe }};
const preselectedId = {% if document and document.category_id %}{{ document.category_id }}{% elif preselected_category_id %}{{ preselected_category_id }}{% else %}null{% endif %};
// Find the root category
const rootCategory = categories.find(c => c.is_root);
let rootCategoryId = null;
if (rootCategory) {
rootCategoryId = rootCategory.id;
}
function addCategoryOptions(categoryList, depth = 0) {
categoryList.forEach(category => {
const option = document.createElement('option');
option.value = category.id;
// Create indentation for hierarchy
const indent = '\u00A0\u00A0\u00A0\u00A0'.repeat(depth);
let prefix = '';
if (depth > 0) {
prefix = '└─ ';
}
option.textContent = indent + prefix + category.name;
// Select this option if it matches the preselected ID
// Or if this is the root category and no preselection was made
option.selected = (category.id == preselectedId) ||
(category.is_root && !preselectedId);
categorySelect.appendChild(option);
// Add children recursively if any
if (category.children && category.children.length > 0) {
addCategoryOptions(category.children, depth + 1);
}
});
}
// Get root categories and their children
const rootCategories = categories.filter(c => c.parent_id === null);
addCategoryOptions(rootCategories);
console.log("Preselected category ID:", preselectedId || (rootCategoryId + " (root by default)"));
}
// Add debounce function for efficiency
function debounce(func, wait) {
let timeout;