Add inline context creation when creating todos

Users can now create a new context directly from the todo creation modal
without having to navigate away to the contexts page.

Changes:
- Added "Create new context..." option to context dropdown in todo modal
- Added inline form that appears when user selects "Create new context"
- Added JavaScript to show/hide the new context input field dynamically
- Added form validation to ensure either an existing context is selected
  or a new context name is provided
- Updated HandleCreateTodo to detect when user wants to create a new context
  (context_id == "__new__") and create it before creating the todo
- New contexts are created with proper position ordering

UX Flow:
1. User clicks "New Todo"
2. User selects "Create new context..." from dropdown
3. Input field appears below for entering context name
4. User enters context name (e.g., "@home", "@work")
5. When form is submitted, context is created first, then todo is created
   with the new context automatically assigned
6. User is redirected back to todos page with both new context and todo visible

This streamlines the workflow and eliminates context switching when users
need to quickly add a todo with a new context.
This commit is contained in:
Claude 2025-11-05 13:21:04 +00:00
parent 51c4b6d3c3
commit d2a9c79633
No known key found for this signature in database
2 changed files with 99 additions and 20 deletions

View file

@ -182,15 +182,19 @@
<div class="form-group">
<label for="context_id">Context *</label>
<select id="context_id" name="context_id" required>
<option value="">Select a context...</option>
<select id="context_id" name="context_id" onchange="handleContextChange()">
<option value="">Select existing context...</option>
{{range .Contexts}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
<option value="__new__"> Create new context...</option>
</select>
{{if not .Contexts}}
<small style="color: var(--text-secondary);">You need to create a context first before creating todos.</small>
{{end}}
</div>
<div class="form-group" id="new_context_group" style="display: none;">
<label for="new_context_name">New Context Name</label>
<input type="text" id="new_context_name" name="new_context_name" placeholder="e.g., @home, @work, @errands">
<small style="color: var(--text-secondary);">Enter a name and it will be created when you save the todo.</small>
</div>
<div class="form-group">
@ -214,12 +218,54 @@
<script>
function openNewTodoModal() {
document.getElementById('newTodoModal').classList.add('active');
// Reset the form
document.getElementById('context_id').value = '';
document.getElementById('new_context_group').style.display = 'none';
document.getElementById('new_context_name').value = '';
}
function closeNewTodoModal() {
document.getElementById('newTodoModal').classList.remove('active');
}
function handleContextChange() {
const contextSelect = document.getElementById('context_id');
const newContextGroup = document.getElementById('new_context_group');
const newContextInput = document.getElementById('new_context_name');
if (contextSelect.value === '__new__') {
// Show the new context input field
newContextGroup.style.display = 'block';
newContextInput.required = true;
newContextInput.focus();
} else {
// Hide the new context input field
newContextGroup.style.display = 'none';
newContextInput.required = false;
newContextInput.value = '';
}
}
// Form validation before submission
document.querySelector('#newTodoModal form').addEventListener('submit', function(e) {
const contextSelect = document.getElementById('context_id');
const newContextName = document.getElementById('new_context_name');
if (contextSelect.value === '__new__') {
if (!newContextName.value.trim()) {
e.preventDefault();
alert('Please enter a name for the new context');
newContextName.focus();
return false;
}
} else if (!contextSelect.value) {
e.preventDefault();
alert('Please select a context or create a new one');
contextSelect.focus();
return false;
}
});
// Close modal when clicking outside
document.getElementById('newTodoModal').addEventListener('click', function(e) {
if (e.target === this) {

View file

@ -327,25 +327,58 @@ func (h *WebHandler) HandleCreateTodo(c *gin.Context) {
notes := c.PostForm("notes")
contextIDStr := c.PostForm("context_id")
newContextName := c.PostForm("new_context_name")
dueDateStr := c.PostForm("due_date")
// Parse context ID (required)
if contextIDStr == "" {
c.Redirect(http.StatusFound, "/todos?error=Context is required")
return
}
var contextID uint
if _, err := fmt.Sscanf(contextIDStr, "%d", &contextID); err != nil {
c.Redirect(http.StatusFound, "/todos?error=Invalid context")
return
}
// Verify context exists and belongs to user
var context models.Context
if err := database.DB.Where("id = ? AND user_id = ?", contextID, user.ID).First(&context).Error; err != nil {
c.Redirect(http.StatusFound, "/todos?error=Context not found")
return
// Check if user wants to create a new context
if contextIDStr == "__new__" {
// Validate new context name
if newContextName == "" {
c.Redirect(http.StatusFound, "/todos?error=New context name is required")
return
}
// Get the highest position value for proper ordering
var maxPosition int
database.DB.Model(&models.Context{}).
Where("user_id = ?", user.ID).
Select("COALESCE(MAX(position), 0)").
Scan(&maxPosition)
// Create new context
newContext := models.Context{
UserID: user.ID,
Name: newContextName,
State: "active",
Position: maxPosition + 1,
}
if err := database.DB.Create(&newContext).Error; err != nil {
c.Redirect(http.StatusFound, "/todos?error=Failed to create context: "+err.Error())
return
}
contextID = newContext.ID
} else {
// Parse existing context ID (required)
if contextIDStr == "" {
c.Redirect(http.StatusFound, "/todos?error=Context is required")
return
}
if _, err := fmt.Sscanf(contextIDStr, "%d", &contextID); err != nil {
c.Redirect(http.StatusFound, "/todos?error=Invalid context")
return
}
// Verify context exists and belongs to user
var context models.Context
if err := database.DB.Where("id = ? AND user_id = ?", contextID, user.ID).First(&context).Error; err != nil {
c.Redirect(http.StatusFound, "/todos?error=Context not found")
return
}
}
// Create todo