Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-01-21 16:57:26 -08:00
commit e4001aba0b
11 changed files with 62 additions and 20 deletions

View file

@ -66,6 +66,7 @@ You'll have to install the Docker and docker-compose. When you're ready, run:
docker-compose build docker-compose build
docker-compose run --rm web python manage.py migrate docker-compose run --rm web python manage.py migrate
docker-compose run --rm web python manage.py initdb docker-compose run --rm web python manage.py initdb
docker-compose up
``` ```
Once the build is complete, you can access the instance at `localhost:1333` Once the build is complete, you can access the instance at `localhost:1333`

View file

@ -31,8 +31,32 @@ window.onload = function() {
// hidden submit button in a form // hidden submit button in a form
document.querySelectorAll('.hidden-form input') document.querySelectorAll('.hidden-form input')
.forEach(t => t.onchange = revealForm); .forEach(t => t.onchange = revealForm);
// polling
document.querySelectorAll('[data-poll]')
.forEach(el => polling(el));
}; };
function polling(el) {
let delay = 10000 + (Math.random() * 1000);
setTimeout(function() {
fetch('/api/updates/' + el.getAttribute('data-poll'))
.then(response => response.json())
.then(data => updateCountElement(el, data));
polling(el);
}, delay, el);
}
function updateCountElement(el, data) {
const currentCount = el.innerText;
const count = data[el.getAttribute('data-poll')];
if (count != currentCount) {
addRemoveClass(el, 'hidden', count < 1);
el.innerText = count;
}
}
function revealForm(e) { function revealForm(e) {
var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0];
if (hidden) { if (hidden) {
@ -88,7 +112,9 @@ function toggleAction(e) {
// set focus, if appropriate // set focus, if appropriate
var focus = el.getAttribute('data-focus-target'); var focus = el.getAttribute('data-focus-target');
if (focus) { if (focus) {
document.getElementById(focus).focus(); var focusEl = document.getElementById(focus);
focusEl.focus();
setTimeout(function(){ focusEl.selectionStart = focusEl.selectionEnd = 10000; }, 0);
} }
} }

View file

@ -106,17 +106,15 @@
</ul> </ul>
</div> </div>
<div class="navbar-item"> <div class="navbar-item">
<a href="/notifications"> <a href="/notifications" class="tags has-addons">
<div class="tags has-addons"> <span class="tag is-medium">
<span class="tag is-medium"> <span class="icon icon-bell" title="Notifications">
<span class="icon icon-bell" title="Notifications"> <span class="is-sr-only">Notifications</span>
<span class="is-sr-only">Notifications</span>
</span>
</span> </span>
{% if request.user|notification_count %} </span>
<span class="tag is-danger is-medium">{{ request.user | notification_count }}</span> <span class="{% if not request.user|notification_count %}hidden {% endif %}tag is-danger is-medium" data-poll="notifications">
{% endif %} {{ request.user | notification_count }}
</div> </span>
</a> </a>
</div> </div>
{% else %} {% else %}

View file

@ -1,3 +1,4 @@
{% load bookwyrm_tags %}
<form class="is-flex-grow-1" name="{{ type }}" action="/post/{{ type }}" method="post" id="tab-{{ type }}-{{ book.id }}{{ reply_parent.id }}"> <form class="is-flex-grow-1" name="{{ type }}" action="/post/{{ type }}" method="post" id="tab-{{ type }}-{{ book.id }}{{ reply_parent.id }}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
@ -34,7 +35,7 @@
<textarea name="quote" class="textarea" id="id_quote_{{ book.id }}_{{ type }}" placeholder="{{ placeholder }}" required></textarea> <textarea name="quote" class="textarea" id="id_quote_{{ book.id }}_{{ type }}" placeholder="{{ placeholder }}" required></textarea>
{% else %} {% else %}
{% include 'snippets/content_warning_field.html' with parent_status=status %} {% include 'snippets/content_warning_field.html' with parent_status=status %}
<textarea name="content" class="textarea" id="id_content_{{ type }}-{{ book.id }}{{reply_parent.id}}" placeholder="{{ placeholder }}" {% if type == 'reply' %} aria-label="Reply"{% endif %} required></textarea> <textarea name="content" class="textarea" id="id_content_{{ type }}-{{ book.id }}{{reply_parent.id}}" placeholder="{{ placeholder }}" {% if type == 'reply' %} aria-label="Reply"{% endif %} required>{% if reply_parent %}{{ reply_parent|mentions:request.user }}{% endif %}</textarea>
{% endif %} {% endif %}
</div> </div>
{% if type == 'quotation' %} {% if type == 'quotation' %}

View file

@ -142,10 +142,10 @@ def get_markdown(content):
@register.filter(name='mentions') @register.filter(name='mentions')
def get_mentions(status, user): def get_mentions(status, user):
''' anyone tagged or replied to in this status ''' ''' people to @ in a reply: the parent and all mentions '''
mentions = set([status.user] + list(status.mention_users.all())) mentions = set([status.user] + list(status.mention_users.all()))
return ' '.join( return ' '.join(
'@' + get_user_identifier(m) for m in mentions if not m == user) '@' + get_user_identifier(m) for m in mentions if not m == user) + ' '
@register.filter(name='status_preview_name') @register.filter(name='status_preview_name')
def get_status_preview_name(obj): def get_status_preview_name(obj):

View file

@ -228,7 +228,7 @@ class TemplateTags(TestCase):
status = models.Status.objects.create( status = models.Status.objects.create(
content='hi', user=self.remote_user) content='hi', user=self.remote_user)
result = bookwyrm_tags.get_mentions(status, self.user) result = bookwyrm_tags.get_mentions(status, self.user)
self.assertEqual(result, '@rat@example.com') self.assertEqual(result, '@rat@example.com ')
def test_get_status_preview_name(self): def test_get_status_preview_name(self):

View file

@ -31,14 +31,15 @@ urlpatterns = [
re_path(r'^inbox/?$', incoming.shared_inbox), re_path(r'^inbox/?$', incoming.shared_inbox),
re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox), re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox),
re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()), re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()),
# .well-known endpoints
re_path(r'^.well-known/webfinger/?$', wellknown.webfinger), re_path(r'^.well-known/webfinger/?$', wellknown.webfinger),
re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer), re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer),
re_path(r'^nodeinfo/2\.0/?$', wellknown.nodeinfo), re_path(r'^nodeinfo/2\.0/?$', wellknown.nodeinfo),
re_path(r'^api/v1/instance/?$', wellknown.instance_info), re_path(r'^api/v1/instance/?$', wellknown.instance_info),
re_path(r'^api/v1/instance/peers/?$', wellknown.peers), re_path(r'^api/v1/instance/peers/?$', wellknown.peers),
# polling updates
re_path('^api/updates/notifications/?$', views.Updates.as_view()),
# authentication # authentication
re_path(r'^login/?$', views.Login.as_view()), re_path(r'^login/?$', views.Login.as_view()),
re_path(r'^register/?$', views.Register.as_view()), re_path(r'^register/?$', views.Register.as_view()),

View file

@ -23,4 +23,5 @@ from .shelf import Shelf
from .shelf import user_shelves_page, create_shelf, delete_shelf from .shelf import user_shelves_page, create_shelf, delete_shelf
from .shelf import shelve, unshelve from .shelf import shelve, unshelve
from .status import Status, Replies, CreateStatus, DeleteStatus from .status import Status, Replies, CreateStatus, DeleteStatus
from .updates import Updates
from .user import User, EditUser, Followers, Following from .user import User, EditUser, Followers, Following

View file

@ -13,7 +13,6 @@ from .helpers import get_activity_feed
# pylint: disable= no-self-use # pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class About(View): class About(View):
''' create invites ''' ''' create invites '''
def get(self, request): def get(self, request):

View file

@ -86,8 +86,6 @@ class CreateStatus(View):
# add reply parent to mentions and notify # add reply parent to mentions and notify
if status.reply_parent: if status.reply_parent:
status.mention_users.add(status.reply_parent.user) status.mention_users.add(status.reply_parent.user)
for mention_user in status.reply_parent.mention_users.all():
status.mention_users.add(mention_user)
if status.reply_parent.user.local: if status.reply_parent.user.local:
create_notification( create_notification(

17
bookwyrm/views/updates.py Normal file
View file

@ -0,0 +1,17 @@
''' endpoints for getting updates about activity '''
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
# pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class Updates(View):
''' so the app can poll '''
def get(self, request):
''' any notifications waiting? '''
return JsonResponse({
'notifications': request.user.notification_set.filter(
read=False
).count(),
})