pin a hashtag

This commit is contained in:
Your Name 2024-06-04 16:51:51 -04:00 committed by Henri Dickson
parent a20779ce31
commit 432b435083
13 changed files with 313 additions and 154 deletions

View file

@ -78,11 +78,11 @@ dialog {
.grid>div:nth-child(2) fieldset { .grid>div:nth-child(2) fieldset {
float: unset; float: unset;
} }
.grid {
grid-row-gap: 0;
}
} }
fieldset {
margin: 0;
}
article { article {
padding-bottom: 1em; padding-bottom: 1em;

View file

@ -1,4 +1,4 @@
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5 {
text-transform: capitalize; text-transform: capitalize;
} }

View file

@ -35,7 +35,7 @@
<img src="{{ identity.avatar|relative_uri }}" alt=""> <img src="{{ identity.avatar|relative_uri }}" alt="">
</a> </a>
</div> </div>
<div> <div style="align-content:center;">
<hgroup> <hgroup>
<h6 class="nickname">{{ identity.display_name }}</h6> <h6 class="nickname">{{ identity.display_name }}</h6>
<div> <div>
@ -68,9 +68,10 @@
{% include 'users/profile_actions.html' %} {% include 'users/profile_actions.html' %}
{% endif %} {% endif %}
</span> </span>
<p> <div>
{{ identity.summary|bleach:"a,p,span,br"|default:"<br>" }} {{ identity.summary|bleach:"a,p,span,br"|default:"" }}
</p> <br>
</div>
</details> </details>
</article> </article>
</section> </section>

View file

@ -0,0 +1,17 @@
# Generated by Django 4.2.13 on 2024-06-04 19:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("journal", "0024_i18n"),
]
operations = [
migrations.AddField(
model_name="tag",
name="pinned",
field=models.BooleanField(default=False, null=True),
),
]

View file

@ -47,8 +47,7 @@ class Tag(List):
title = models.CharField( title = models.CharField(
max_length=100, null=False, blank=False, validators=TagValidators max_length=100, null=False, blank=False, validators=TagValidators
) )
# TODO case convert and space removal on save pinned = models.BooleanField(default=False, null=True)
# TODO check on save
class Meta: class Meta:
unique_together = [["owner", "title"]] unique_together = [["owner", "title"]]
@ -61,7 +60,27 @@ class Tag(List):
@staticmethod @staticmethod
def deep_cleanup_title(title): def deep_cleanup_title(title):
"""Remove all non-word characters, only for public index purpose""" """Remove all non-word characters, only for public index purpose"""
return re.sub(r"\W+", " ", title).rstrip().lstrip("# ").lower() or "_" return re.sub(r"\W+", " ", title).rstrip().lstrip("# ").lower()[:100] or "_"
def update(
self, title: str, visibility: int | None = None, pinned: bool | None = None
):
old_title = Tag.deep_cleanup_title(self.title)
new_title = Tag.deep_cleanup_title(title)
was_pinned = bool(self.pinned)
if visibility is not None:
self.visibility = 2 if visibility else 0
if pinned is not None:
self.pinned = pinned
self.title = title
self.save()
if was_pinned != self.pinned or (old_title != new_title and self.pinned):
from takahe.utils import Takahe
if was_pinned:
Takahe.unpin_hashtag_for_user(self.owner.pk, old_title)
if self.pinned:
Takahe.pin_hashtag_for_user(self.owner.pk, new_title)
class TagManager: class TagManager:

View file

@ -4,72 +4,57 @@
{% load humanize %} {% load humanize %}
{% load mastodon %} {% load mastodon %}
{% load thumb %} {% load thumb %}
<div id="modal" <dialog open
_="on closeModal add .closing then wait for animationend then remove me"> class="tag-editor"
<div class="modal-underlay" _="on click trigger closeModal"></div> _="on close_dialog add .closing then wait for animationend then remove me">
<div class="modal-content"> <article>
<div class="add-to-list-modal__head"> <header>
<span class="add-to-list-modal__title">{% trans 'Tag' %} - {{ item.title }} - {% trans 'Edit' %}</span> <link to="#"
<span class="add-to-list-modal__close-button modal-close" aria-label="Close"
_="on click trigger closeModal"> class="close"
<i class="fa-solid fa-xmark"></i> _="on click trigger close_dialog" />
</span> <strong>{% trans 'Edit' %} {{ tag.title }}</strong>
</div> </header>
<div class="add-to-list-modal__body"> <div>
<form action="{% url 'journal:user_tag_edit' %}?tag={{ tag.title }}" <form action="{% url 'journal:user_tag_edit' %}?tag={{ tag.title }}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="id" value="{{ tag.id }}"> <input type="hidden" name="id" value="{{ tag.id }}">
<div class="mark-modal__tag"> <input name="title" type="text" value="{{ tag.title }}" required>
<div class="tag-input"> <fieldset>
<input name="title" type="text" value="{{ tag.title }}" required> <label for="_pinned">
</div> <input role="switch"
</div> type="checkbox"
<div class="mark-modal__option"> name="pinned"
<div class="mark-modal__visibility-radio"> value="1"
<span>{% trans "Visibility" %} {% if tag.pinned %}checked{% endif %}
<ul id="id_visibility"> id="_pinned">
<li> {% trans "Pin" %}
<label for="id_visibility_0"> </label>
<input type="radio" </fieldset>
name="visibility" <fieldset>
value="0" <input type="radio"
required="" name="visibility"
id="id_visibility_0" value="0"
{% if tag.visibility == 0 %}checked{% endif %}> required=""
{% trans "Public" %} id="id_visibility_0"
</label> {% if tag.visibility == 0 %}checked{% endif %}>
</li> <label for="id_visibility_0">{% trans "Public" %}</label>
<li> <input type="radio"
<label for="id_visibility_2"> name="visibility"
<input type="radio" value="2"
name="visibility" required=""
value="2" id="id_visibility_2"
required="" {% if tag.visibility != 0 %}checked{% endif %}>
id="id_visibility_2" <label for="id_visibility_2">{% trans "Personal" %}</label>
{% if tag.visibility != 0 %}checked{% endif %}> </fieldset>
{% trans "Personal" %} <input type="submit" class="button float-right" value="{% trans "Save" %}">
</label> <small>{% trans "Personal tags are not shown to others when they view your tag list, unless you pin them. However, if you use this tag when marking an item publicly, it might still be visible to others." %}</small>
</li> <label for="_delete">
</ul> <input type="checkbox" name="delete" value="1" id="_delete">
</span> {% trans "Delete this tag" %}
</div> </label>
<i>{% trans "Personal tags are not shown to others when they view your tag list. However, if you use this tag when marking an item publicly, it might still be visible to others." %}</i>
</div>
<div class="mark-modal__confirm-button">
<input type="submit" class="button float-right" value="{% trans "Save" %}">
</div>
<div class="mark-modal__option">
<div class="mark-modal__visibility-radio">
<span>
<label for="_delete">
<input type="checkbox" name="delete" value="1" id="_delete">
{% trans "Delete this tag" %}
</label>
</span>
</div>
</div>
</form> </form>
</div> </div>
</div> </article>
</div> </dialog>

View file

@ -15,7 +15,7 @@
{% include "_header.html" %} {% include "_header.html" %}
<main> <main>
<div class="grid__main"> <div class="grid__main">
<h5 class="large-only"> <h5>
{% block head %}{{ identity.display_name }}{% endblock %} {% block head %}{{ identity.display_name }}{% endblock %}
</h5> </h5>
<div> <div>

View file

@ -68,10 +68,11 @@ def user_tag_edit(request):
): ):
msg.error(request.user, _("Duplicated tag.")) msg.error(request.user, _("Duplicated tag."))
return HttpResponseRedirect(request.META.get("HTTP_REFERER")) return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
tag.title = tag_title tag.update(
tag.visibility = int(request.POST.get("visibility", 0)) tag_title,
tag.visibility = 0 if tag.visibility == 0 else 2 int(request.POST.get("visibility", 0)),
tag.save() bool(request.POST.get("pinned", 0)),
)
msg.info(request.user, _("Tag updated.")) msg.info(request.user, _("Tag updated."))
return redirect( return redirect(
reverse( reverse(

View file

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-04 10:15-0400\n" "POT-Creation-Date: 2024-06-04 16:48-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -667,7 +667,7 @@ msgstr "不再提示"
#: catalog/templates/_item_comments_by_episode.html:78 #: catalog/templates/_item_comments_by_episode.html:78
#: catalog/templates/_item_reviews.html:43 #: catalog/templates/_item_reviews.html:43
#: catalog/templates/podcast_episode_data.html:41 #: catalog/templates/podcast_episode_data.html:41
#: common/templates/_sidebar.html:203 #: common/templates/_sidebar.html:204
msgid "show more" msgid "show more"
msgstr "显示更多" msgstr "显示更多"
@ -1018,7 +1018,7 @@ msgstr "否"
#: journal/templates/collection.html:132 #: journal/templates/collection.html:132
#: journal/templates/collection_edit.html:9 #: journal/templates/collection_edit.html:9
#: journal/templates/collection_edit.html:25 journal/templates/review.html:80 #: journal/templates/collection_edit.html:25 journal/templates/review.html:80
#: journal/templates/tag_edit.html:12 #: journal/templates/tag_edit.html:16
msgid "Edit" msgid "Edit"
msgstr "编辑" msgstr "编辑"
@ -1031,7 +1031,7 @@ msgstr "创建"
#: journal/templates/add_to_collection.html:35 #: journal/templates/add_to_collection.html:35
#: journal/templates/collection_edit.html:38 journal/templates/comment.html:69 #: journal/templates/collection_edit.html:38 journal/templates/comment.html:69
#: journal/templates/mark.html:147 journal/templates/review_edit.html:39 #: journal/templates/mark.html:147 journal/templates/review_edit.html:39
#: journal/templates/tag_edit.html:60 users/templates/users/account.html:43 #: journal/templates/tag_edit.html:51 users/templates/users/account.html:43
#: users/templates/users/account.html:104 #: users/templates/users/account.html:104
#: users/templates/users/preferences.html:168 #: users/templates/users/preferences.html:168
#: users/templates/users/preferences.html:193 #: users/templates/users/preferences.html:193
@ -1112,8 +1112,8 @@ msgstr "热门标签"
#: catalog/templates/discover.html:185 catalog/templates/item_base.html:236 #: catalog/templates/discover.html:185 catalog/templates/item_base.html:236
#: catalog/templates/item_mark_list.html:54 #: catalog/templates/item_mark_list.html:54
#: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:98 #: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:99
#: common/templates/_sidebar.html:198 #: common/templates/_sidebar.html:199
#: common/templates/_sidebar_anonymous.html:43 #: common/templates/_sidebar_anonymous.html:43
#: common/templates/_sidebar_anonymous.html:58 #: common/templates/_sidebar_anonymous.html:58
#: journal/templates/collection_items.html:8 journal/templates/posts.html:45 #: journal/templates/collection_items.html:8 journal/templates/posts.html:45
@ -1544,31 +1544,31 @@ msgstr "设置用户名"
msgid "approving followers manually" msgid "approving followers manually"
msgstr "已开启关注审核" msgstr "已开启关注审核"
#: common/templates/_sidebar.html:83 #: common/templates/_sidebar.html:84
msgid "Current Targets" msgid "Current Targets"
msgstr "当前目标" msgstr "当前目标"
#: common/templates/_sidebar.html:96 #: common/templates/_sidebar.html:97
msgid "Set a collection as target, its progress will show up here." msgid "Set a collection as target, its progress will show up here."
msgstr "将自己或他人的收藏单设为目标,这里就会显示进度" msgstr "将自己或他人的收藏单设为目标,这里就会显示进度"
#: common/templates/_sidebar.html:109 #: common/templates/_sidebar.html:110
msgid "Recent podcast episodes" msgid "Recent podcast episodes"
msgstr "近期播客节目" msgstr "近期播客节目"
#: common/templates/_sidebar.html:144 #: common/templates/_sidebar.html:145
msgid "Currently reading" msgid "Currently reading"
msgstr "正在阅读" msgstr "正在阅读"
#: common/templates/_sidebar.html:167 #: common/templates/_sidebar.html:168
msgid "Currently watching" msgid "Currently watching"
msgstr "正在追看" msgstr "正在追看"
#: common/templates/_sidebar.html:190 #: common/templates/_sidebar.html:191
msgid "Common Tags" msgid "Common Tags"
msgstr "常用标签" msgstr "常用标签"
#: common/templates/_sidebar.html:214 #: common/templates/_sidebar.html:215
msgid "Recent Posts" msgid "Recent Posts"
msgstr "近期帖文" msgstr "近期帖文"
@ -1722,8 +1722,8 @@ msgstr "发布到联邦宇宙"
#: journal/forms.py:25 journal/forms.py:45 #: journal/forms.py:25 journal/forms.py:45
#: journal/templates/collection_share.html:24 #: journal/templates/collection_share.html:24
#: journal/templates/tag_edit.html:30 journal/templates/wrapped_share.html:36 #: journal/templates/wrapped_share.html:36 users/templates/users/data.html:38
#: users/templates/users/data.html:38 users/templates/users/data.html:130 #: users/templates/users/data.html:130
msgid "Visibility" msgid "Visibility"
msgstr "可见性" msgstr "可见性"
@ -1756,7 +1756,7 @@ msgstr "备注"
#: journal/models/common.py:28 journal/templates/collection_share.html:35 #: journal/models/common.py:28 journal/templates/collection_share.html:35
#: journal/templates/comment.html:35 journal/templates/mark.html:93 #: journal/templates/comment.html:35 journal/templates/mark.html:93
#: journal/templates/tag_edit.html:40 journal/templates/wrapped_share.html:43 #: journal/templates/tag_edit.html:42 journal/templates/wrapped_share.html:43
#: users/templates/users/data.html:47 users/templates/users/data.html:139 #: users/templates/users/data.html:47 users/templates/users/data.html:139
#: users/templates/users/preferences.html:54 #: users/templates/users/preferences.html:54
msgid "Public" msgid "Public"
@ -2493,7 +2493,7 @@ msgstr "日历"
msgid "annual summary" msgid "annual summary"
msgstr "年度小结" msgstr "年度小结"
#: journal/templates/profile.html:131 mastodon/api.py:680 #: journal/templates/profile.html:131 mastodon/api.py:670
msgid "collection" msgid "collection"
msgstr "收藏单" msgstr "收藏单"
@ -2521,19 +2521,19 @@ msgstr "保存时将行首空格替换为全角"
msgid "change review date" msgid "change review date"
msgstr "指定评论日期" msgstr "指定评论日期"
#: journal/templates/tag_edit.html:12 #: journal/templates/tag_edit.html:32
msgid "Tag" msgid "Pin"
msgstr "标签" msgstr "置顶"
#: journal/templates/tag_edit.html:51 #: journal/templates/tag_edit.html:49
msgid "Personal" msgid "Personal"
msgstr "个人" msgstr "个人"
#: journal/templates/tag_edit.html:57 #: journal/templates/tag_edit.html:52
msgid "Personal tags are not shown to others when they view your tag list. However, if you use this tag when marking an item publicly, it might still be visible to others." msgid "Personal tags are not shown to others when they view your tag list, unless you pin them. However, if you use this tag when marking an item publicly, it might still be visible to others."
msgstr "个人标签仅限于在个人主页的标签列表里不向他人展示,如果公开标记一个条目时使用这个标签仍会被别人看到。" msgstr "个人标签不被包括在条目的公共索引中,但如果公开标记一个条目时使用这个标签仍会被别人看到。"
#: journal/templates/tag_edit.html:67 #: journal/templates/tag_edit.html:55
msgid "Delete this tag" msgid "Delete this tag"
msgstr "删除这个标签" msgstr "删除这个标签"
@ -2685,7 +2685,7 @@ msgstr "标签已删除"
msgid "Duplicated tag." msgid "Duplicated tag."
msgstr "重复标签" msgstr "重复标签"
#: journal/views/tag.py:75 #: journal/views/tag.py:76
msgid "Tag updated." msgid "Tag updated."
msgstr "标签已更新" msgstr "标签已更新"
@ -2698,11 +2698,11 @@ msgstr "总结已发布到时间轴"
msgid "regarding {item_title}, may contain spoiler or triggering content" msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "关于 {item_title},可能包含剧透或敏感内容" msgstr "关于 {item_title},可能包含剧透或敏感内容"
#: mastodon/api.py:685 #: mastodon/api.py:675
msgid "shared my collection" msgid "shared my collection"
msgstr "分享我的收藏单" msgstr "分享我的收藏单"
#: mastodon/api.py:688 #: mastodon/api.py:678
#, python-brace-format #, python-brace-format
msgid "shared {username}'s collection" msgid "shared {username}'s collection"
msgstr "分享 {username} 的收藏单" msgstr "分享 {username} 的收藏单"
@ -2964,31 +2964,31 @@ msgstr "提及"
msgid "follow" msgid "follow"
msgstr "关注" msgstr "关注"
#: takahe/models.py:427 #: takahe/models.py:430
msgid "Display Name" msgid "Display Name"
msgstr "昵称" msgstr "昵称"
#: takahe/models.py:429 #: takahe/models.py:432
msgid "Bio" msgid "Bio"
msgstr "简介" msgstr "简介"
#: takahe/models.py:431 #: takahe/models.py:434
msgid "Manually approve new followers" msgid "Manually approve new followers"
msgstr "手工审核关注者" msgstr "手工审核关注者"
#: takahe/models.py:435 #: takahe/models.py:438
msgid "Include profile and posts in discovery" msgid "Include profile and posts in discovery"
msgstr "允许个人资料和帖文包含在发现中" msgstr "允许个人资料和帖文包含在发现中"
#: takahe/models.py:438 #: takahe/models.py:441
msgid "Include posts in search results" msgid "Include posts in search results"
msgstr "允许个人帖文包含在搜索结果中" msgstr "允许个人帖文包含在搜索结果中"
#: takahe/models.py:456 #: takahe/models.py:459
msgid "Profile picture" msgid "Profile picture"
msgstr "头像" msgstr "头像"
#: takahe/models.py:463 #: takahe/models.py:466
msgid "Header picture" msgid "Header picture"
msgstr "背景图片" msgstr "背景图片"

View file

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-04 10:15-0400\n" "POT-Creation-Date: 2024-06-04 16:48-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -667,7 +667,7 @@ msgstr "不再提示"
#: catalog/templates/_item_comments_by_episode.html:78 #: catalog/templates/_item_comments_by_episode.html:78
#: catalog/templates/_item_reviews.html:43 #: catalog/templates/_item_reviews.html:43
#: catalog/templates/podcast_episode_data.html:41 #: catalog/templates/podcast_episode_data.html:41
#: common/templates/_sidebar.html:203 #: common/templates/_sidebar.html:204
msgid "show more" msgid "show more"
msgstr "顯示更多" msgstr "顯示更多"
@ -1018,7 +1018,7 @@ msgstr "否"
#: journal/templates/collection.html:132 #: journal/templates/collection.html:132
#: journal/templates/collection_edit.html:9 #: journal/templates/collection_edit.html:9
#: journal/templates/collection_edit.html:25 journal/templates/review.html:80 #: journal/templates/collection_edit.html:25 journal/templates/review.html:80
#: journal/templates/tag_edit.html:12 #: journal/templates/tag_edit.html:16
msgid "Edit" msgid "Edit"
msgstr "編輯" msgstr "編輯"
@ -1031,7 +1031,7 @@ msgstr "創建"
#: journal/templates/add_to_collection.html:35 #: journal/templates/add_to_collection.html:35
#: journal/templates/collection_edit.html:38 journal/templates/comment.html:69 #: journal/templates/collection_edit.html:38 journal/templates/comment.html:69
#: journal/templates/mark.html:147 journal/templates/review_edit.html:39 #: journal/templates/mark.html:147 journal/templates/review_edit.html:39
#: journal/templates/tag_edit.html:60 users/templates/users/account.html:43 #: journal/templates/tag_edit.html:51 users/templates/users/account.html:43
#: users/templates/users/account.html:104 #: users/templates/users/account.html:104
#: users/templates/users/preferences.html:168 #: users/templates/users/preferences.html:168
#: users/templates/users/preferences.html:193 #: users/templates/users/preferences.html:193
@ -1112,8 +1112,8 @@ msgstr "熱門標籤"
#: catalog/templates/discover.html:185 catalog/templates/item_base.html:236 #: catalog/templates/discover.html:185 catalog/templates/item_base.html:236
#: catalog/templates/item_mark_list.html:54 #: catalog/templates/item_mark_list.html:54
#: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:98 #: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:99
#: common/templates/_sidebar.html:198 #: common/templates/_sidebar.html:199
#: common/templates/_sidebar_anonymous.html:43 #: common/templates/_sidebar_anonymous.html:43
#: common/templates/_sidebar_anonymous.html:58 #: common/templates/_sidebar_anonymous.html:58
#: journal/templates/collection_items.html:8 journal/templates/posts.html:45 #: journal/templates/collection_items.html:8 journal/templates/posts.html:45
@ -1544,31 +1544,31 @@ msgstr "設置用戶名"
msgid "approving followers manually" msgid "approving followers manually"
msgstr "已開啓關注審覈" msgstr "已開啓關注審覈"
#: common/templates/_sidebar.html:83 #: common/templates/_sidebar.html:84
msgid "Current Targets" msgid "Current Targets"
msgstr "當前目標" msgstr "當前目標"
#: common/templates/_sidebar.html:96 #: common/templates/_sidebar.html:97
msgid "Set a collection as target, its progress will show up here." msgid "Set a collection as target, its progress will show up here."
msgstr "將自己或他人的收藏單設爲目標,這裏就會顯示進度" msgstr "將自己或他人的收藏單設爲目標,這裏就會顯示進度"
#: common/templates/_sidebar.html:109 #: common/templates/_sidebar.html:110
msgid "Recent podcast episodes" msgid "Recent podcast episodes"
msgstr "近期播客節目" msgstr "近期播客節目"
#: common/templates/_sidebar.html:144 #: common/templates/_sidebar.html:145
msgid "Currently reading" msgid "Currently reading"
msgstr "正在閱讀" msgstr "正在閱讀"
#: common/templates/_sidebar.html:167 #: common/templates/_sidebar.html:168
msgid "Currently watching" msgid "Currently watching"
msgstr "正在追看" msgstr "正在追看"
#: common/templates/_sidebar.html:190 #: common/templates/_sidebar.html:191
msgid "Common Tags" msgid "Common Tags"
msgstr "常用標籤" msgstr "常用標籤"
#: common/templates/_sidebar.html:214 #: common/templates/_sidebar.html:215
msgid "Recent Posts" msgid "Recent Posts"
msgstr "近期帖文" msgstr "近期帖文"
@ -1722,8 +1722,8 @@ msgstr "發佈到聯邦宇宙"
#: journal/forms.py:25 journal/forms.py:45 #: journal/forms.py:25 journal/forms.py:45
#: journal/templates/collection_share.html:24 #: journal/templates/collection_share.html:24
#: journal/templates/tag_edit.html:30 journal/templates/wrapped_share.html:36 #: journal/templates/wrapped_share.html:36 users/templates/users/data.html:38
#: users/templates/users/data.html:38 users/templates/users/data.html:130 #: users/templates/users/data.html:130
msgid "Visibility" msgid "Visibility"
msgstr "可見性" msgstr "可見性"
@ -1756,7 +1756,7 @@ msgstr "備註"
#: journal/models/common.py:28 journal/templates/collection_share.html:35 #: journal/models/common.py:28 journal/templates/collection_share.html:35
#: journal/templates/comment.html:35 journal/templates/mark.html:93 #: journal/templates/comment.html:35 journal/templates/mark.html:93
#: journal/templates/tag_edit.html:40 journal/templates/wrapped_share.html:43 #: journal/templates/tag_edit.html:42 journal/templates/wrapped_share.html:43
#: users/templates/users/data.html:47 users/templates/users/data.html:139 #: users/templates/users/data.html:47 users/templates/users/data.html:139
#: users/templates/users/preferences.html:54 #: users/templates/users/preferences.html:54
msgid "Public" msgid "Public"
@ -2493,7 +2493,7 @@ msgstr "日曆"
msgid "annual summary" msgid "annual summary"
msgstr "年度小結" msgstr "年度小結"
#: journal/templates/profile.html:131 mastodon/api.py:680 #: journal/templates/profile.html:131 mastodon/api.py:670
msgid "collection" msgid "collection"
msgstr "收藏單" msgstr "收藏單"
@ -2521,19 +2521,19 @@ msgstr "保存時將行首空格替換爲全角"
msgid "change review date" msgid "change review date"
msgstr "指定評論日期" msgstr "指定評論日期"
#: journal/templates/tag_edit.html:12 #: journal/templates/tag_edit.html:32
msgid "Tag" msgid "Pin"
msgstr "標籤" msgstr "置頂"
#: journal/templates/tag_edit.html:51 #: journal/templates/tag_edit.html:49
msgid "Personal" msgid "Personal"
msgstr "個人" msgstr "個人"
#: journal/templates/tag_edit.html:57 #: journal/templates/tag_edit.html:52
msgid "Personal tags are not shown to others when they view your tag list. However, if you use this tag when marking an item publicly, it might still be visible to others." msgid "Personal tags are not shown to others when they view your tag list, unless you pin them. However, if you use this tag when marking an item publicly, it might still be visible to others."
msgstr "個人標籤僅限於在個人主頁的標籤列表裏不向他人展示,如果公開標記一個條目時使用這個標籤仍會被別人看到。" msgstr "個人標籤不被包括在條目的公共索引中,但如果公開標記一個條目時使用這個標籤仍會被別人看到。"
#: journal/templates/tag_edit.html:67 #: journal/templates/tag_edit.html:55
msgid "Delete this tag" msgid "Delete this tag"
msgstr "刪除這個標籤" msgstr "刪除這個標籤"
@ -2685,7 +2685,7 @@ msgstr "標籤已刪除"
msgid "Duplicated tag." msgid "Duplicated tag."
msgstr "重複標籤" msgstr "重複標籤"
#: journal/views/tag.py:75 #: journal/views/tag.py:76
msgid "Tag updated." msgid "Tag updated."
msgstr "標籤已更新" msgstr "標籤已更新"
@ -2698,11 +2698,11 @@ msgstr "總結已發佈到時間軸"
msgid "regarding {item_title}, may contain spoiler or triggering content" msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "關於 {item_title},可能包含劇透或敏感內容" msgstr "關於 {item_title},可能包含劇透或敏感內容"
#: mastodon/api.py:685 #: mastodon/api.py:675
msgid "shared my collection" msgid "shared my collection"
msgstr "分享我的收藏單" msgstr "分享我的收藏單"
#: mastodon/api.py:688 #: mastodon/api.py:678
#, python-brace-format #, python-brace-format
msgid "shared {username}'s collection" msgid "shared {username}'s collection"
msgstr "分享 {username} 的收藏單" msgstr "分享 {username} 的收藏單"
@ -2964,31 +2964,31 @@ msgstr "提及"
msgid "follow" msgid "follow"
msgstr "關注" msgstr "關注"
#: takahe/models.py:427 #: takahe/models.py:430
msgid "Display Name" msgid "Display Name"
msgstr "暱稱" msgstr "暱稱"
#: takahe/models.py:429 #: takahe/models.py:432
msgid "Bio" msgid "Bio"
msgstr "簡介" msgstr "簡介"
#: takahe/models.py:431 #: takahe/models.py:434
msgid "Manually approve new followers" msgid "Manually approve new followers"
msgstr "手工審覈關注者" msgstr "手工審覈關注者"
#: takahe/models.py:435 #: takahe/models.py:438
msgid "Include profile and posts in discovery" msgid "Include profile and posts in discovery"
msgstr "允許個人資料和帖文包含在發現中" msgstr "允許個人資料和帖文包含在發現中"
#: takahe/models.py:438 #: takahe/models.py:441
msgid "Include posts in search results" msgid "Include posts in search results"
msgstr "允許個人帖文包含在搜索結果中" msgstr "允許個人帖文包含在搜索結果中"
#: takahe/models.py:456 #: takahe/models.py:459
msgid "Profile picture" msgid "Profile picture"
msgstr "頭像" msgstr "頭像"
#: takahe/models.py:463 #: takahe/models.py:466
msgid "Header picture" msgid "Header picture"
msgstr "背景圖片" msgstr "背景圖片"

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.13 on 2024-06-01 05:38 # Generated by Django 4.2.13 on 2024-06-04 19:33
import functools import functools
@ -102,6 +102,11 @@ class Migration(migrations.Migration):
("public", models.BooleanField(null=True)), ("public", models.BooleanField(null=True)),
("state", models.CharField(default="outdated", max_length=100)), ("state", models.CharField(default="outdated", max_length=100)),
("state_changed", models.DateTimeField(auto_now_add=True)), ("state_changed", models.DateTimeField(auto_now_add=True)),
("state_next_attempt", models.DateTimeField(blank=True, null=True)),
(
"state_locked_until",
models.DateTimeField(blank=True, db_index=True, null=True),
),
("stats", models.JSONField(blank=True, null=True)), ("stats", models.JSONField(blank=True, null=True)),
("stats_updated", models.DateTimeField(blank=True, null=True)), ("stats_updated", models.DateTimeField(blank=True, null=True)),
("aliases", models.JSONField(blank=True, null=True)), ("aliases", models.JSONField(blank=True, null=True)),
@ -162,8 +167,7 @@ class Migration(migrations.Migration):
( (
"indexable", "indexable",
models.BooleanField( models.BooleanField(
default=True, default=True, verbose_name="Include posts in search results"
verbose_name="Include posts in search results",
), ),
), ),
( (
@ -589,6 +593,40 @@ class Migration(migrations.Migration):
blank=True, related_name="identities", to="takahe.user" blank=True, related_name="identities", to="takahe.user"
), ),
), ),
migrations.CreateModel(
name="HashtagFeature",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
(
"hashtag",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="featurers",
to="takahe.hashtag",
),
),
(
"identity",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="hashtag_features",
to="takahe.identity",
),
),
],
options={
"db_table": "users_hashtagfeature",
},
),
migrations.CreateModel( migrations.CreateModel(
name="FanOut", name="FanOut",
fields=[ fields=[

View file

@ -5,7 +5,7 @@ import re
import secrets import secrets
import ssl import ssl
import time import time
from datetime import date from datetime import date, timedelta
from functools import cached_property, partial from functools import cached_property, partial
from typing import TYPE_CHECKING, Literal, Optional from typing import TYPE_CHECKING, Literal, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
@ -385,7 +385,10 @@ class Identity(models.Model):
Represents both local and remote Fediverse identities (actors) Represents both local and remote Fediverse identities (actors)
""" """
domain_id: str if TYPE_CHECKING:
domain_id: str
inbound_follows: "models.QuerySet[Follow]"
hashtag_features: "models.QuerySet[HashtagFeature]"
class Restriction(models.IntegerChoices): class Restriction(models.IntegerChoices):
none = 0 none = 0
@ -729,6 +732,34 @@ class Identity(models.Model):
self.shared_inbox_uri = f"https://{self.domain.uri_domain}/inbox/" self.shared_inbox_uri = f"https://{self.domain.uri_domain}/inbox/"
self.save() self.save()
def get_remote_targets(self):
"""
Returns an iterable with Identities of followers that have unique
shared_inbox among each other to be used as target.
"""
if not self.local:
return []
remote_follower_ids = Follow.objects.filter(
target=self,
target__local=False,
state__in=["unrequested", "pending_approval", "accepting", "accepted"],
).values_list("source", flat=True)
deduped_targets = set()
shared_inboxes = set()
for target in Identity.objects.filter(pk__in=remote_follower_ids):
if not target.shared_inbox_uri:
deduped_targets.add(target)
elif target.shared_inbox_uri not in shared_inboxes:
shared_inboxes.add(target.shared_inbox_uri)
deduped_targets.add(target)
return deduped_targets
def fanout(self, type: str, **kwargs):
for target in self.get_remote_targets():
FanOut.objects.create(
identity=target, subject_identity=self, type=type, **kwargs
)
class Follow(models.Model): class Follow(models.Model):
""" """
@ -1569,6 +1600,8 @@ class Hashtag(models.Model):
# state = StateField(HashtagStates) # state = StateField(HashtagStates)
state = models.CharField(max_length=100, default="outdated") state = models.CharField(max_length=100, default="outdated")
state_changed = models.DateTimeField(auto_now_add=True) state_changed = models.DateTimeField(auto_now_add=True)
state_next_attempt = models.DateTimeField(blank=True, null=True)
state_locked_until = models.DateTimeField(null=True, blank=True, db_index=True)
# Metrics for this Hashtag # Metrics for this Hashtag
stats = models.JSONField(null=True, blank=True) stats = models.JSONField(null=True, blank=True)
@ -1639,6 +1672,53 @@ class Hashtag(models.Model):
results[date(year, month, day)] = val results[date(year, month, day)] = val
return dict(sorted(results.items(), reverse=True)[:num]) return dict(sorted(results.items(), reverse=True)[:num])
@property
def needs_update(self):
if self.stats_updated is None:
return True
return timezone.now() - self.stats_updated > timedelta(hours=1)
@classmethod
def ensure_hashtag(cls, name, update=None):
"""
Properly strips/trims/lowercases the hashtag name, and makes sure a Hashtag
object exists in the database, and returns it.
"""
name = name.strip().lstrip("#").lower()[: Hashtag.MAXIMUM_LENGTH]
hashtag, created = cls.objects.get_or_create(hashtag=name)
if created or update or hashtag.needs_update:
hashtag.state = "outdated"
hashtag.state_changed = timezone.now()
hashtag.state_next_attempt = None
hashtag.state_locked_until = None
hashtag.save(
update_fields=[
"state",
"state_changed",
"state_next_attempt",
"state_locked_until",
]
)
return hashtag
class HashtagFeature(models.Model):
identity = models.ForeignKey(
"takahe.Identity",
on_delete=models.CASCADE,
related_name="hashtag_features",
)
hashtag = models.ForeignKey(
"takahe.Hashtag",
on_delete=models.CASCADE,
related_name="featurers",
)
created = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "users_hashtagfeature"
class PostInteraction(models.Model): class PostInteraction(models.Model):
""" """

View file

@ -990,3 +990,21 @@ class Takahe:
else: else:
qs = qs.filter(visibility__in=[0, 1, 4]) qs = qs.filter(visibility__in=[0, 1, 4])
return qs.prefetch_related("attachments", "author") return qs.prefetch_related("attachments", "author")
@staticmethod
def pin_hashtag_for_user(identity_pk: int, hashtag: str):
tag = Hashtag.ensure_hashtag(hashtag)
identity = Identity.objects.get(pk=identity_pk)
feature, created = identity.hashtag_features.get_or_create(hashtag=tag)
if created:
identity.fanout("tag_featured", subject_hashtag=tag)
@staticmethod
def unpin_hashtag_for_user(identity_pk: int, hashtag: str):
identity = Identity.objects.get(pk=identity_pk)
featured = HashtagFeature.objects.filter(
identity=identity, hashtag_id=hashtag
).first()
if featured:
identity.fanout("tag_unfeatured", subject_hashtag_id=hashtag)
featured.delete()