1
0
Fork 0

Merge pull request #2524 from chdorner/feature/tag-support

Initial hashtag support
This commit is contained in:
Mouse Reeve 2023-03-12 16:37:39 -07:00 committed by GitHub
commit 12af5992a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 635 additions and 13 deletions

View file

@ -130,6 +130,7 @@ from .group import (
accept_membership,
reject_membership,
)
from .hashtag import Hashtag
from .inbox import Inbox
from .interaction import Favorite, Unfavorite, Boost, Unboost
from .isbn import Isbn

54
bookwyrm/views/hashtag.py Normal file
View file

@ -0,0 +1,54 @@
""" listing statuses for a given hashtag """
from django.core.paginator import Paginator
from django.db.models import Q
from django.views import View
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from bookwyrm import models
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.views.helpers import maybe_redirect_local_path
# pylint: disable= no-self-use
class Hashtag(View):
"""listing statuses for a given hashtag"""
# pylint: disable=unused-argument
def get(self, request, hashtag_id, slug=None):
"""show hashtag with related statuses"""
hashtag = get_object_or_404(models.Hashtag, id=hashtag_id)
if redirect_local_path := maybe_redirect_local_path(request, hashtag):
return redirect_local_path
activities = (
models.Status.privacy_filter(
request.user,
)
.filter(
Q(mention_hashtags=hashtag),
)
.exclude(
privacy__in=["direct", "unlisted"],
)
.select_related(
"user",
"reply_parent",
"review__book",
"comment__book",
"quotation__book",
)
.prefetch_related(
"mention_books",
"mention_users",
"attachments",
)
)
paginated = Paginator(activities, PAGE_LENGTH)
data = {
"hashtag": hashtag.name,
"activities": paginated.get_page(request.GET.get("page", 1)),
}
return TemplateResponse(request, "hashtag.html", data)

View file

@ -115,6 +115,19 @@ class CreateStatus(View):
if status.reply_parent:
status.mention_users.add(status.reply_parent.user)
# inspect the text for hashtags
for (mention_text, mention_hashtag) in find_or_create_hashtags(content).items():
# add them to status mentions fk
status.mention_hashtags.add(mention_hashtag)
# turn the mention into a link
content = re.sub(
rf"{mention_text}\b(?!@)",
rf'<a href="{mention_hashtag.remote_id}" data-mention="hashtag">'
+ rf"{mention_text}</a>",
content,
)
# deduplicate mentions
status.mention_users.set(set(status.mention_users.all()))
@ -237,6 +250,38 @@ def find_mentions(user, content):
return username_dict
def find_or_create_hashtags(content):
"""detect #hashtags in raw status content
it stores hashtags case-sensitive, but ensures that an existing
hashtag with different case are found and re-used. for example,
an existing #BookWyrm hashtag will be found and used even if the
status content is using #bookwyrm.
"""
if not content:
return {}
found_hashtags = {t.lower(): t for t in re.findall(regex.HASHTAG, content)}
if len(found_hashtags) == 0:
return {}
known_hashtags = {
t.name.lower(): t
for t in models.Hashtag.objects.filter(
Q(name__in=found_hashtags.keys())
).distinct()
}
not_found = found_hashtags.keys() - known_hashtags.keys()
for lower_name in not_found:
tag_name = found_hashtags[lower_name]
mention_hashtag = models.Hashtag(name=tag_name)
mention_hashtag.save()
known_hashtags[lower_name] = mention_hashtag
return {found_hashtags[k]: v for k, v in known_hashtags.items()}
def format_links(content):
"""detect and format links"""
validator = URLValidator()