Add infinity scrolling for /feeds
This commit is contained in:
parent
07f7011a68
commit
d97f980b66
7 changed files with 67 additions and 33 deletions
|
|
@ -65,6 +65,7 @@ INSTALLED_APPS: list[str] = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.sitemaps",
|
"django.contrib.sitemaps",
|
||||||
|
"django_htmx",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE: list[str] = [
|
MIDDLEWARE: list[str] = [
|
||||||
|
|
@ -77,6 +78,7 @@ MIDDLEWARE: list[str] = [
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"django_htmx.middleware.HtmxMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Use PostgreSQL as the default database
|
# Use PostgreSQL as the default database
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
|
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
from django.core.paginator import EmptyPage, Paginator
|
||||||
from django.db.models.manager import BaseManager
|
from django.db.models.manager import BaseManager
|
||||||
from django.http import FileResponse, Http404, HttpRequest, HttpResponse
|
from django.http import FileResponse, Http404, HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
@ -20,7 +21,6 @@ from django.template import loader
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from django.views.generic.list import ListView
|
|
||||||
|
|
||||||
from feedvault.feeds import add_url
|
from feedvault.feeds import add_url
|
||||||
from feedvault.models import Domain, Entry, Feed, FeedAddResult, UserUploadedFile
|
from feedvault.models import Domain, Entry, Feed, FeedAddResult, UserUploadedFile
|
||||||
|
|
@ -33,6 +33,10 @@ if TYPE_CHECKING:
|
||||||
logger: logging.Logger = logging.getLogger(__name__)
|
logger: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HtmxHttpRequest(HttpRequest):
|
||||||
|
htmx: Any
|
||||||
|
|
||||||
|
|
||||||
class IndexView(View):
|
class IndexView(View):
|
||||||
"""Index path."""
|
"""Index path."""
|
||||||
|
|
||||||
|
|
@ -74,24 +78,33 @@ class FeedView(View):
|
||||||
return render(request, "feed.html", context)
|
return render(request, "feed.html", context)
|
||||||
|
|
||||||
|
|
||||||
class FeedsView(ListView):
|
class FeedsView(View):
|
||||||
"""All feeds."""
|
"""All feeds."""
|
||||||
|
|
||||||
model = Feed
|
def get(self, request: HtmxHttpRequest) -> HttpResponse:
|
||||||
paginate_by = 100
|
"""All feeds."""
|
||||||
template_name = "feeds.html"
|
feeds: BaseManager[Feed] = Feed.objects.only("id", "feed_url")
|
||||||
context_object_name = "feeds"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs) -> dict: # noqa: ANN003
|
paginator = Paginator(object_list=feeds, per_page=100)
|
||||||
"""Get the context data."""
|
page_number = int(request.GET.get("page", default=1))
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
feed_amount: int = Feed.objects.count() or 0
|
try:
|
||||||
context["description"] = f"Archiving {feed_amount} feeds"
|
pages = paginator.get_page(page_number)
|
||||||
context["keywords"] = "feed, rss, atom, archive, rss list"
|
except EmptyPage:
|
||||||
context["author"] = "TheLovinator"
|
return HttpResponse("")
|
||||||
context["canonical"] = "https://feedvault.se/feeds/"
|
|
||||||
context["title"] = "Feeds"
|
context = {
|
||||||
return context
|
"feeds": pages,
|
||||||
|
"description": "An archive of all feeds",
|
||||||
|
"keywords": "feed, rss, atom, archive, rss list",
|
||||||
|
"author": "TheLovinator",
|
||||||
|
"canonical": "https://feedvault.se/feeds/",
|
||||||
|
"title": "Feeds",
|
||||||
|
"page": page_number,
|
||||||
|
}
|
||||||
|
|
||||||
|
template_name = "partials/feeds.html" if request.htmx else "feeds.html"
|
||||||
|
return render(request, template_name, context)
|
||||||
|
|
||||||
|
|
||||||
class AddView(LoginRequiredMixin, View):
|
class AddView(LoginRequiredMixin, View):
|
||||||
|
|
|
||||||
17
poetry.lock
generated
17
poetry.lock
generated
|
|
@ -463,6 +463,21 @@ files = [
|
||||||
django = ">=3.2.4"
|
django = ">=3.2.4"
|
||||||
sqlparse = ">=0.2"
|
sqlparse = ">=0.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-htmx"
|
||||||
|
version = "1.17.3"
|
||||||
|
description = "Extensions for using Django with htmx."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "django-htmx-1.17.3.tar.gz", hash = "sha256:a2069219920d7ef0883ddbf5e8d931069db145a0d4a8a032a2708f840c7a68a6"},
|
||||||
|
{file = "django_htmx-1.17.3-py3-none-any.whl", hash = "sha256:0de964ca257eda2a4ebeeaa8181320119378fa5f95a2fc2f2bfbdd35034ed424"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asgiref = ">=3.6"
|
||||||
|
Django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-ninja"
|
name = "django-ninja"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -1299,4 +1314,4 @@ brotli = ["Brotli"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "67041176bd9ca4289c6900d794465da1df4a966ed81625367552fa238b7976f8"
|
content-hash = "e21c650c4ac009f1e6ba8939aa6236a63b8dc3a095b803fe7e13a529fc62b072"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.12"
|
python = "^3.12"
|
||||||
django = {extras = ["argon2"], version = "^5.0.3"}
|
django = { extras = ["argon2"], version = "^5.0.3" }
|
||||||
python-dotenv = "^1.0.1"
|
python-dotenv = "^1.0.1"
|
||||||
feedparser = "^6.0.11"
|
feedparser = "^6.0.11"
|
||||||
gunicorn = "^21.2.0"
|
gunicorn = "^21.2.0"
|
||||||
|
|
@ -15,9 +15,10 @@ dateparser = "^1.2.0"
|
||||||
discord-webhook = "^1.3.1"
|
discord-webhook = "^1.3.1"
|
||||||
django-ninja = "^1.1.0"
|
django-ninja = "^1.1.0"
|
||||||
django-debug-toolbar = "^4.3.0"
|
django-debug-toolbar = "^4.3.0"
|
||||||
whitenoise = {extras = ["brotli"], version = "^6.6.0"}
|
whitenoise = { extras = ["brotli"], version = "^6.6.0" }
|
||||||
rich = "^13.7.1"
|
rich = "^13.7.1"
|
||||||
psycopg = {extras = ["binary"], version = "^3.1.18"}
|
psycopg = { extras = ["binary"], version = "^3.1.18" }
|
||||||
|
django-htmx = "^1.17.3"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.3.0"
|
ruff = "^0.3.0"
|
||||||
|
|
@ -25,9 +26,7 @@ djlint = "^1.34.1"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
requires = [
|
requires = ["poetry-core"]
|
||||||
"poetry-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
exclude = ["migrations"]
|
exclude = ["migrations"]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
{% if author %}<meta name="author" content="{{ author }}" />{% endif %}
|
{% if author %}<meta name="author" content="{{ author }}" />{% endif %}
|
||||||
{% if canonical %}<link rel="canonical" href="{{ canonical }}" />{% endif %}
|
{% if canonical %}<link rel="canonical" href="{{ canonical }}" />{% endif %}
|
||||||
<title>{{ title|default:"FeedVault" }}</title>
|
<title>{{ title|default:"FeedVault" }}</title>
|
||||||
<script src="{% static 'htmx.min.js' %}"></script>
|
<script src="{% static 'htmx.min.js' %}" defer></script>
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
max-width: 88ch;
|
max-width: 88ch;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Latest Feeds</h2>
|
<h2>Latest Feeds</h2>
|
||||||
{% if feeds %}
|
{% include "partials/feeds.html" %}
|
||||||
{% for feed in feeds %}
|
|
||||||
<a href="{% url 'feed' feed.id %}">{{ feed.feed_url|default:"Unknown Feed" }} →</a>
|
|
||||||
<br>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p>No feeds yet. Time to add some!</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
12
templates/partials/feeds.html
Normal file
12
templates/partials/feeds.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% if feeds %}
|
||||||
|
{% for feed in feeds %}
|
||||||
|
<a href="{% url 'feed' feed.id %}">{{ feed.feed_url|default:"Unknown Feed" }} →</a>
|
||||||
|
<br>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>No feeds yet. Time to add some!</p>
|
||||||
|
{% endif %}
|
||||||
|
<div hx-get="{% url 'feeds' %}?page={{ page|add:1 }}"
|
||||||
|
hx-trigger="revealed"
|
||||||
|
hx-target="this"
|
||||||
|
hx-swap="outerHTML">Loading...</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue