v0.4.0: live feed updates (auto-insert new posts)
New artifacts appear at the top of the feed without a manual refresh. - GET /feed/updates (session-authed): new artifacts since a timestamp marker, owner-scoped and filter-aware (source/label/kind/q), returned as row HTML. - Feed JS polls every 5s, paused while the tab is hidden (immediate poll on refocus). Prepends new rows into today's group without touching the selected row or the preview iframe — what you're reading stays put and selected. - Fresh rows show "now", aging into their real time after ~3 min; brief highlight on arrival. - Extracted _artifact_rows.html partial, shared by the day-group render and the updates endpoint, so live rows match exactly. - Polling chosen over SSE/WebSockets: zero infra on sync-WSGI + SQLite (see planning/next_steps for the SSE upgrade path). 142 tests pass; feed page + endpoint verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f6f004c689
commit
cae414c307
9 changed files with 289 additions and 40 deletions
|
|
@ -22,9 +22,10 @@ from django.db import OperationalError, transaction
|
|||
from django.db.models import Count, Q, Sum
|
||||
from django.http import FileResponse, Http404, HttpResponse, HttpResponseForbidden, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.template.defaultfilters import date as date_filter, slugify
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.dateparse import parse_date
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
|
|
@ -573,6 +574,8 @@ def feed(request):
|
|||
"day_groups": day_groups,
|
||||
"older_count": older_count,
|
||||
"window_start": window_start,
|
||||
# Marker for live polling: newest artifact shown, else now.
|
||||
"feed_since": (window_artifacts[0].created_at if window_artifacts else timezone.now()).isoformat(),
|
||||
"source_counts": source_counts,
|
||||
"total_count": source_scoped.filter(read_at__isnull=True).count(),
|
||||
"all_labels": labels_with_colors_list(all_labels, overrides),
|
||||
|
|
@ -666,6 +669,58 @@ def feed_older(request):
|
|||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def feed_updates(request):
|
||||
"""New artifacts since a timestamp, as row HTML for the feed to prepend live.
|
||||
Owner-scoped and filter-aware — respects the source/label/kind/q the browser
|
||||
is currently viewing, so a new post that doesn't match won't appear."""
|
||||
after = parse_datetime(request.GET.get("after", "") or "")
|
||||
if after is None:
|
||||
return JsonResponse({"count": 0})
|
||||
|
||||
selected_source = request.GET.get("source", "")
|
||||
selected_label = request.GET.get("label", "")
|
||||
selected_kind = request.GET.get("kind", "")
|
||||
title_query = request.GET.get("q", "")
|
||||
artifacts = apply_feed_filters(
|
||||
Artifact.objects.filter(owner=request.user),
|
||||
selected_source,
|
||||
selected_label,
|
||||
selected_kind,
|
||||
title_query,
|
||||
).filter(created_at__gt=after).order_by("-created_at")
|
||||
|
||||
new = list(artifacts[:100])
|
||||
if not new:
|
||||
return JsonResponse({"count": 0})
|
||||
|
||||
overrides = tag_color_overrides()
|
||||
for artifact in new:
|
||||
artifact.label_chips = tags_with_colors(artifact.tags, overrides)
|
||||
|
||||
html = render_to_string(
|
||||
"artifacts/_artifact_rows.html",
|
||||
{
|
||||
"artifacts": new,
|
||||
"selected_artifact": None,
|
||||
"selected_source": selected_source,
|
||||
"selected_label": selected_label,
|
||||
"selected_kind": selected_kind,
|
||||
"title_query": title_query,
|
||||
"selected_view": "raw" if request.GET.get("view") == "raw" else "rendered",
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
"count": len(new),
|
||||
"newest": new[0].created_at.isoformat(),
|
||||
"today_label": date_filter(timezone.localdate(), "D, M j Y"),
|
||||
"html": html,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@xframe_options_sameorigin
|
||||
def artifact_view(request, artifact_id):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue