feat(tags): Add tag archive route and admin interface integration
Implement Phase 3 of v1.3.0 tags feature per microformats-tags-design.md: Routes (starpunk/routes/public.py): - Add /tag/<tag> archive route with normalization and 404 handling - Pre-load tags in index route for all notes - Pre-load tags in note route for individual notes Admin (starpunk/routes/admin.py): - Parse comma-separated tag input in create route - Parse tag input in update route - Pre-load tags when displaying edit form - Empty tag field removes all tags Templates: - Add tag input field to templates/admin/edit.html - Add tag input field to templates/admin/new.html - Use Jinja2 map filter to display existing tags Implementation details: - Tag URL parameter normalized to lowercase before lookup - Tags pre-loaded using object.__setattr__ pattern (like media) - parse_tag_input() handles trim, dedupe, normalization - All existing tests pass (micropub categories, admin routes) Per architect design: - No pagination on tag archives (acceptable for v1.3.0) - No autocomplete in admin (out of scope) - Follows existing media loading patterns Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,7 @@ def create_note_submit():
|
||||
custom_slug: Optional custom slug (v1.2.0 Phase 1)
|
||||
media_files: Multiple file upload (v1.2.0 Phase 3)
|
||||
captions[]: Captions for each media file (v1.2.0 Phase 3)
|
||||
tags: Comma-separated tag list (v1.3.0 Phase 3)
|
||||
|
||||
Returns:
|
||||
Redirect to dashboard on success, back to form on error
|
||||
@@ -85,21 +86,27 @@ def create_note_submit():
|
||||
Decorator: @require_auth
|
||||
"""
|
||||
from starpunk.media import save_media, attach_media_to_note
|
||||
from starpunk.tags import parse_tag_input
|
||||
|
||||
content = request.form.get("content", "").strip()
|
||||
published = "published" in request.form
|
||||
custom_slug = request.form.get("custom_slug", "").strip()
|
||||
tags_input = request.form.get("tags", "")
|
||||
|
||||
if not content:
|
||||
flash("Content cannot be empty", "error")
|
||||
return redirect(url_for("admin.new_note_form"))
|
||||
|
||||
# Parse tags (v1.3.0 Phase 3)
|
||||
tags = parse_tag_input(tags_input)
|
||||
|
||||
try:
|
||||
# Create note first (per Q4)
|
||||
note = create_note(
|
||||
content,
|
||||
published=published,
|
||||
custom_slug=custom_slug if custom_slug else None
|
||||
custom_slug=custom_slug if custom_slug else None,
|
||||
tags=tags if tags else None
|
||||
)
|
||||
|
||||
# Handle media uploads (v1.2.0 Phase 3)
|
||||
@@ -167,12 +174,18 @@ def edit_note_form(note_id: int):
|
||||
Decorator: @require_auth
|
||||
Template: templates/admin/edit.html
|
||||
"""
|
||||
from starpunk.tags import get_note_tags
|
||||
|
||||
note = get_note(id=note_id)
|
||||
|
||||
if not note:
|
||||
flash("Note not found", "error")
|
||||
return redirect(url_for("admin.dashboard")), 404
|
||||
|
||||
# Pre-load tags for the edit form (v1.3.0 Phase 3)
|
||||
tags = get_note_tags(note.id)
|
||||
object.__setattr__(note, '_cached_tags', tags)
|
||||
|
||||
return render_template("admin/edit.html", note=note)
|
||||
|
||||
|
||||
@@ -191,12 +204,15 @@ def update_note_submit(note_id: int):
|
||||
Form data:
|
||||
content: Updated markdown content (required)
|
||||
published: Checkbox for published status (optional)
|
||||
tags: Comma-separated tag list (v1.3.0 Phase 3)
|
||||
|
||||
Returns:
|
||||
Redirect to dashboard on success, back to form on error
|
||||
|
||||
Decorator: @require_auth
|
||||
"""
|
||||
from starpunk.tags import parse_tag_input
|
||||
|
||||
# Check if note exists first
|
||||
existing_note = get_note(id=note_id, load_content=False)
|
||||
if not existing_note:
|
||||
@@ -205,13 +221,22 @@ def update_note_submit(note_id: int):
|
||||
|
||||
content = request.form.get("content", "").strip()
|
||||
published = "published" in request.form
|
||||
tags_input = request.form.get("tags", "")
|
||||
|
||||
if not content:
|
||||
flash("Content cannot be empty", "error")
|
||||
return redirect(url_for("admin.edit_note_form", note_id=note_id))
|
||||
|
||||
# Parse tags (v1.3.0 Phase 3)
|
||||
tags = parse_tag_input(tags_input)
|
||||
|
||||
try:
|
||||
note = update_note(id=note_id, content=content, published=published)
|
||||
note = update_note(
|
||||
id=note_id,
|
||||
content=content,
|
||||
published=published,
|
||||
tags=tags if tags else None
|
||||
)
|
||||
flash(f"Note updated: {note.slug}", "success")
|
||||
return redirect(url_for("admin.dashboard"))
|
||||
except ValueError as e:
|
||||
|
||||
Reference in New Issue
Block a user