193 lines
No EOL
9.1 KiB
HTML
193 lines
No EOL
9.1 KiB
HTML
{% 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 %} |