notification

This commit is contained in:
Your Name 2024-04-19 20:24:34 -04:00 committed by Henri Dickson
parent 05821eaac1
commit 15a7b55642
31 changed files with 379 additions and 47 deletions

View file

@ -66,6 +66,12 @@
// margin-right: 4px;
}
blockquote {
padding-top: 0;
padding-bottom: 0;
margin: 0;
}
.spacing {
margin-bottom: calc(var(--pico-block-spacing-horizontal)/2);
}

View file

@ -78,6 +78,9 @@
</summary>
<ul role="listbox" style="min-width:-webkit-max-content;" dir="rtl">
{% if request.user.is_authenticated %}
<li>
<a href="{% url 'social:notification' %}">通知</a>
</li>
<li>
<a href="{% url 'users:data' %}">数据</a>
</li>

View file

@ -178,6 +178,11 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
obj = None
return obj
@classmethod
def get_by_post_id(cls, post_id: int):
pp = PiecePost.objects.filter(post_id=post_id).first()
return pp.piece if pp else None
@classmethod
def update_by_ap_object(cls, owner, item, obj, post_id, visibility):
raise NotImplementedError("subclass must implement this")

View file

@ -1,3 +1,4 @@
{% load i18n %}
<section class="replies" hx-target="this" hx-swap="outerHTML">
{% for post in replies %}
<div>
@ -35,8 +36,9 @@
<div id="replies_{{ post.pk }}"></div>
</div>
{% empty %}
<div class="empty">暂无回应</div>
<div class="empty">{% trans 'nothing so far.' %}</div>
{% endfor %}
{% if post %}
<form class="reply"
role="group"
hx-post="{% url 'journal:post_reply' post.pk %}"
@ -80,4 +82,5 @@
<i class="fa-regular fa-paper-plane"></i>
</button>
</form>
{% endif %}
</section>

View file

@ -0,0 +1 @@
unsupported notification: announcement

View file

@ -0,0 +1,5 @@
{% load bleach_tags %}
boosted your post
<blockquote class="tldr" _="on click toggle .tldr on me">
{{ event.post.content|bleach:"a,p,span,br,div,img"|default:"<br>" }}
</blockquote>

View file

@ -0,0 +1 @@
boosted your collection <a href="{{ event.piece.url }}">{{ event.piece.title }}</a>

View file

@ -0,0 +1,8 @@
{% load duration %}
boost your comment on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.rating_grade %}{{ event.piece.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.html|safe }}
</blockquote>

View file

@ -0,0 +1 @@
boosted your review <a href="{{ event.piece.url }}">{{ event.piece.title }}</a> on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>

View file

@ -0,0 +1,8 @@
{% load duration %}
boosted your mark on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.mark.rating_grade %}{{ event.piece.mark.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.mark.comment.html|safe }}
</blockquote>

View file

@ -0,0 +1,12 @@
{% load bleach_tags %}
requested to follow you
<blockquote class="tldr" _="on click toggle .tldr on me">
<span class="action">{% include 'users/profile_actions.html' with show_home=1 identity=event.identity %}</span>
<div>
<code class="{{ event.identity.id }}_handler"
style="cursor:pointer"
onmouseleave="$(this).removeAttr('data-tooltip')"
onclick="navigator.clipboard.writeText(this.innerText);$(this).data('tooltip','copied');">@{{ event.identity.full_handle }}</code>
</div>
{{ event.identity.summary|bleach:"a,p,span,br"|default:"<br>" }}
</blockquote>

View file

@ -0,0 +1,12 @@
{% load bleach_tags %}
followed you
<blockquote class="tldr" _="on click toggle .tldr on me">
<span class="action">{% include 'users/profile_actions.html' with show_home=1 identity=event.identity %}</span>
<div>
<code class="{{ event.identity.id }}_handler"
style="cursor:pointer"
onmouseleave="$(this).removeAttr('data-tooltip')"
onclick="navigator.clipboard.writeText(this.innerText);$(this).data('tooltip','copied');">@{{ event.identity.full_handle }}</code>
</div>
{{ event.identity.summary|bleach:"a,p,span,br"|default:"<br>" }}
</blockquote>

View file

@ -0,0 +1 @@
joined {{ site_name }}

View file

@ -0,0 +1,5 @@
{% load bleach_tags %}
liked your post
<blockquote class="tldr" _="on click toggle .tldr on me">
{{ event.post.content|bleach:"a,p,span,br,div,img"|default:"<br>" }}
</blockquote>

View file

@ -0,0 +1 @@
liked your collection <a href="{{ event.piece.url }}">{{ event.piece.title }}</a>

View file

@ -0,0 +1,8 @@
{% load duration %}
liked your comment on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.rating_grade %}{{ event.piece.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.html|safe }}
</blockquote>

View file

@ -0,0 +1 @@
liked your review <a href="{{ event.piece.url }}">{{ event.piece.title }}</a> on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>

View file

@ -0,0 +1,8 @@
{% load duration %}
liked your mark on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.mark.rating_grade %}{{ event.piece.mark.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.mark.comment.html|safe }}
</blockquote>

View file

@ -0,0 +1,6 @@
{% load bleach_tags %}
mentioned you
<blockquote class="tldr" _="on click toggle .tldr on me">
{{ event.post.content|bleach:"a,p,span,br,div,img"|default:"<br>" }}
</blockquote>
{% include "events/_post.html" with post=event.reply %}

View file

@ -0,0 +1,2 @@
replied to your collection <a href="{{ event.piece.url }}">{{ event.piece.title }}</a>
{% include "events/_post.html" with post=event.reply %}

View file

@ -0,0 +1,9 @@
{% load duration %}
replied to your comment on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.rating_grade %}{{ event.piece.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.html|safe }}
</blockquote>
{% include "events/_post.html" with post=event.reply %}

View file

@ -0,0 +1,2 @@
replied to your review <a href="{{ event.piece.url }}">{{ event.piece.title }}</a> on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
{% include "events/_post.html" with post=event.reply %}

View file

@ -0,0 +1,9 @@
{% load duration %}
replied to your mark on <a href="{{ event.item.url }}">{{ event.item.display_title }}</a>
<blockquote class="tldr" _="on click toggle .tldr on me">
<span>
{% if event.piece.mark.rating_grade %}{{ event.piece.mark.rating_grade|rating_star }}{% endif %}
</span>
{{ event.piece.mark.comment.html|safe }}
</blockquote>
{% include "events/_post.html" with post=event.reply %}

View file

@ -0,0 +1 @@
unsupported notification: post

View file

@ -0,0 +1,51 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
{% load prettydate %}
{% load user_actions %}
{% load duration %}
{% for event in events %}
<section class="activity">
<div class="avatar">
<img src="{{ event.identity.avatar }}" alt="cover" />
</div>
<div>
<div>
<span class="time">
<span>{{ event.created|naturaltime }}</span>
</span>
<div>
<span>
<a href="{{ event.identity.url }}"
class="nickname"
title="@{{ event.identity.full_handle }}">{{ event.identity.display_name }}</a>
</span>
{% with "event/"|add:event.template|add:".html" as template %}
{% include template %}
{% endwith %}
</div>
</div>
</div>
</section>
{% if forloop.last %}
<div class="htmx-indicator"
style="margin-left: 60px"
hx-get="{% url 'social:events' %}?last={{ event.created|date:'Y-m-d H:i:s.uO'|urlencode }}"
hx-trigger="revealed"
hx-swap="outerHTML">
<i class="fa-solid fa-compact-disc fa-spin loading"></i>
</div>
{% endif %}
{% empty %}
{% if request.GET.last %}
<div class="empty">{% trans 'nothing more.' %}</div>
{% else %}
<div class="empty">{% trans 'nothing so far.' %}</div>
{% endif %}
{% endfor %}

View file

@ -0,0 +1,17 @@
<div>
<span class="action">
{% include "action_reply_post.html" %}
{% include "action_like_post.html" %}
{% include "action_boost_post.html" %}
{% include "action_open_post.html" %}
</span>
{% if post.summary %}
<details>
<summary>{{ post.summary }}</summary>
{{ post.safe_content_local }}
</details>
{% else %}
{{ post.safe_content_local }}
{% endif %}
<div id="replies_{{ post.pk }}"></div>
</div>

View file

@ -0,0 +1,38 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="zh" class="feed-page">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }} - {% trans 'Notification' %}</title>
{% include "common_libs.html" %}
<script src="{{ cdn_url }}/npm/shikwasa@2.2.1/dist/shikwasa.min.js"></script>
<link href="{{ cdn_url }}/npm/shikwasa@2.2.1/dist/style.min.css"
rel="stylesheet"></link>
<script src="{% static 'js/podcast.js' %}"></script>
</head>
<body>
{% include "_header.html" with current="timeline" %}
<main>
<div class="grid__main">
<h5>{% trans 'Notification' %}</h5>
<div class="feed">
<div hx-get="{% url 'social:events' %}"
hx-trigger="intersect once delay:0.1s"
hx-swap="outerHTML">
<i class="fa-solid fa-compact-disc fa-spin loading"></i>
</div>
</div>
</div>
{% include "_sidebar.html" with show_progress=1 identity=request.user.identity %}
</main>
{% include "_footer.html" %}
</body>
</html>

View file

@ -6,4 +6,6 @@ app_name = "social"
urlpatterns = [
path("", feed, name="feed"),
path("data", data, name="data"),
path("notification", notification, name="notification"),
path("events", events, name="events"),
]

View file

@ -1,7 +1,6 @@
import logging
from django.contrib.auth.decorators import login_required
from django.core.exceptions import BadRequest
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@ -9,11 +8,10 @@ from django.views.decorators.http import require_http_methods
from catalog.models import *
from journal.models import *
from takahe.utils import Takahe
from .models import *
_logger = logging.getLogger(__name__)
PAGE_SIZE = 10
@ -71,3 +69,84 @@ def data(request):
)[:PAGE_SIZE],
},
)
@require_http_methods(["GET"])
@login_required
def notification(request):
if not request.user.registration_complete:
return redirect(reverse("users:register"))
user = request.user
podcast_ids = [
p.item_id
for p in user.shelf_manager.get_latest_members(
ShelfType.PROGRESS, ItemCategory.Podcast
)
]
recent_podcast_episodes = PodcastEpisode.objects.filter(
program_id__in=podcast_ids
).order_by("-pub_date")[:10]
books_in_progress = Edition.objects.filter(
id__in=[
p.item_id
for p in user.shelf_manager.get_latest_members(
ShelfType.PROGRESS, ItemCategory.Book
)[:10]
]
)
tvshows_in_progress = Item.objects.filter(
id__in=[
p.item_id
for p in user.shelf_manager.get_latest_members(
ShelfType.PROGRESS, ItemCategory.TV
)[:10]
]
)
return render(
request,
"notification.html",
{
"recent_podcast_episodes": recent_podcast_episodes,
"books_in_progress": books_in_progress,
"tvshows_in_progress": tvshows_in_progress,
},
)
class NotificationEvent:
def __init__(self, tle) -> None:
self.event = tle
self.type = tle.type
self.template = tle.type
self.created = tle.created
self.identity = (
APIdentity.objects.filter(pk=tle.subject_identity.pk).first()
if tle.subject_identity
else None
)
self.post = tle.subject_post
if self.type == "mentioned":
# for reply, self.post is the original post
self.reply = self.post
self.replies = [self.post]
self.post = self.post.in_reply_to_post() if self.post else None
self.piece = Piece.get_by_post_id(self.post.id) if self.post else None
self.item = getattr(self.piece, "item") if hasattr(self.piece, "item") else None
if self.piece and self.template in ["liked", "boost", "mentioned"]:
cls = self.piece.__class__.__name__.lower()
self.template += "_" + cls
@login_required
@require_http_methods(["GET"])
def events(request):
es = Takahe.get_events(request.user.identity.pk)
last = request.GET.get("last")
if last:
es = es.filter(created__lt=last)
nes = [NotificationEvent(e) for e in es[:PAGE_SIZE]]
return render(
request,
"events.html",
{"events": nes},
)

View file

@ -942,3 +942,27 @@ class Takahe:
.exclude(seen=user)
):
a.seen.add(user)
@staticmethod
def get_events(identity_id: int):
return (
TimelineEvent.objects.select_related(
"subject_post",
"subject_post__author",
"subject_post__author__domain",
"subject_identity",
"subject_identity__domain",
"subject_post_interaction",
"subject_post_interaction__identity",
"subject_post_interaction__identity__domain",
)
.prefetch_related(
"subject_post__attachments",
"subject_post__mentions",
"subject_post__emojis",
)
.filter(identity=identity_id)
.exclude(type="post", subject_identity__isnull=True)
.exclude(subject_identity_id=identity_id)
.order_by("-created")
)

View file

@ -31,10 +31,13 @@ _RESERVED_USERNAMES = [
"__",
"admin",
"administrator",
"service",
"support",
"system",
"user",
"users",
"api",
"bot",
"me",
]