Merge branch 'main' into load_remote_statuses

This commit is contained in:
Mouse Reeve 2020-11-06 15:48:17 -08:00 committed by GitHub
commit 275527f255
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 249 additions and 244 deletions

View file

@ -1,6 +1,6 @@
''' handles all of the activity coming in to the server ''' ''' handles all of the activity coming in to the server '''
import json import json
from urllib.parse import urldefrag from urllib.parse import urldefrag, unquote_plus
import django.db.utils import django.db.utils
from django.http import HttpResponse from django.http import HttpResponse
@ -286,9 +286,10 @@ def handle_boost(activity):
@app.task @app.task
def handle_unboost(activity): def handle_unboost(activity):
''' someone gave us a boost! ''' ''' someone gave us a boost! '''
boost = models.Boost.objects.filter(remote_id=activity['object']['id']).first() boost = models.Boost.objects.filter(
if not boost: remote_id=activity['object']['id']
return ).first()
if boost:
status_builder.delete_status(boost) status_builder.delete_status(boost)
@ -297,8 +298,15 @@ def handle_tag(activity):
''' someone is tagging a book ''' ''' someone is tagging a book '''
user = get_or_create_remote_user(activity['actor']) user = get_or_create_remote_user(activity['actor'])
if not user.local: if not user.local:
book = activity['target']['id'] # ordered collection weirndess so we can't just to_model
status_builder.create_tag(user, book, activity['object']['name']) book = books_manager.get_or_create_book(activity['object']['id'])
name = activity['object']['target'].split('/')[-1]
name = unquote_plus(name)
models.Tag.objects.get_or_create(
user=user,
book=book,
name=name
)
@app.task @app.task

View file

@ -35,8 +35,8 @@ class Tag(OrderedCollectionMixin, BookWyrmModel):
return activitypub.Add( return activitypub.Add(
id='%s#add' % self.remote_id, id='%s#add' % self.remote_id,
actor=user.remote_id, actor=user.remote_id,
object=self.book.to_activity(), object=self.book.local_id,
target=self.to_activity(), target=self.remote_id,
).serialize() ).serialize()
def to_remove_activity(self, user): def to_remove_activity(self, user):

View file

@ -136,6 +136,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
''' an ordered collection of statuses ''' ''' an ordered collection of statuses '''
queryset = Status.objects.filter( queryset = Status.objects.filter(
user=self, user=self,
deleted=False,
).select_subclasses() ).select_subclasses()
return self.to_ordered_collection(queryset, \ return self.to_ordered_collection(queryset, \
remote_id=self.outbox, **kwargs) remote_id=self.outbox, **kwargs)

View file

@ -9,7 +9,7 @@ import requests
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast from bookwyrm.broadcast import broadcast
from bookwyrm.status import create_tag, create_notification from bookwyrm.status import create_notification
from bookwyrm.status import create_generated_note from bookwyrm.status import create_generated_note
from bookwyrm.status import delete_status from bookwyrm.status import delete_status
from bookwyrm.remote_user import get_or_create_remote_user from bookwyrm.remote_user import get_or_create_remote_user
@ -257,9 +257,8 @@ def handle_status(user, form):
broadcast(user, remote_activity, software='other') broadcast(user, remote_activity, software='other')
def handle_tag(user, book, name): def handle_tag(user, tag):
''' tag a book ''' ''' tag a book '''
tag = create_tag(user, book, name)
broadcast(user, tag.to_add_activity(user)) broadcast(user, tag.to_add_activity(user))

View file

@ -30,6 +30,7 @@
<glyph unicode="&#xe914;" glyph-name="home" horiz-adv-x="951" d="M804.571 384v-274.286c0-20-16.571-36.571-36.571-36.571h-219.429v219.429h-146.286v-219.429h-219.429c-20 0-36.571 16.571-36.571 36.571v274.286c0 1.143 0.571 2.286 0.571 3.429l328.571 270.857 328.571-270.857c0.571-1.143 0.571-2.286 0.571-3.429zM932 423.428l-35.429-42.286c-2.857-3.429-7.429-5.714-12-6.286h-1.714c-4.571 0-8.571 1.143-12 4l-395.429 329.714-395.429-329.714c-4-2.857-8.571-4.571-13.714-4-4.571 0.571-9.143 2.857-12 6.286l-35.429 42.286c-6.286 7.429-5.143 19.429 2.286 25.714l410.857 342.286c24 20 62.857 20 86.857 0l139.429-116.571v111.429c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-233.143l125.143-104c7.429-6.286 8.571-18.286 2.286-25.714z" /> <glyph unicode="&#xe914;" glyph-name="home" horiz-adv-x="951" d="M804.571 384v-274.286c0-20-16.571-36.571-36.571-36.571h-219.429v219.429h-146.286v-219.429h-219.429c-20 0-36.571 16.571-36.571 36.571v274.286c0 1.143 0.571 2.286 0.571 3.429l328.571 270.857 328.571-270.857c0.571-1.143 0.571-2.286 0.571-3.429zM932 423.428l-35.429-42.286c-2.857-3.429-7.429-5.714-12-6.286h-1.714c-4.571 0-8.571 1.143-12 4l-395.429 329.714-395.429-329.714c-4-2.857-8.571-4.571-13.714-4-4.571 0.571-9.143 2.857-12 6.286l-35.429 42.286c-6.286 7.429-5.143 19.429 2.286 25.714l410.857 342.286c24 20 62.857 20 86.857 0l139.429-116.571v111.429c0 10.286 8 18.286 18.286 18.286h109.714c10.286 0 18.286-8 18.286-18.286v-233.143l125.143-104c7.429-6.286 8.571-18.286 2.286-25.714z" />
<glyph unicode="&#xe915;" glyph-name="local" horiz-adv-x="1097" d="M338.857 438.857c-59.429-1.714-113.143-27.429-151.429-73.143h-76.571c-57.143 0-110.857 27.429-110.857 90.857 0 46.286-1.714 201.714 70.857 201.714 12 0 71.429-48.571 148.571-48.571 26.286 0 51.429 4.571 76 13.143-1.714-12.571-2.857-25.143-2.857-37.714 0-52 16.571-103.429 46.286-146.286zM950.857 74.857c0-92.571-61.143-148-152.571-148h-499.429c-91.429 0-152.571 55.429-152.571 148 0 129.143 30.286 327.429 197.714 327.429 19.429 0 90.286-79.429 204.571-79.429s185.143 79.429 204.571 79.429c167.429 0 197.714-198.286 197.714-327.429zM365.714 804.571c0-80.571-65.714-146.286-146.286-146.286s-146.286 65.714-146.286 146.286 65.714 146.286 146.286 146.286 146.286-65.714 146.286-146.286zM768 585.143c0-121.143-98.286-219.429-219.429-219.429s-219.429 98.286-219.429 219.429 98.286 219.429 219.429 219.429 219.429-98.286 219.429-219.429zM1097.143 456.571c0-63.429-53.714-90.857-110.857-90.857h-76.571c-38.286 45.714-92 71.429-151.429 73.143 29.714 42.857 46.286 94.286 46.286 146.286 0 12.571-1.143 25.143-2.857 37.714 24.571-8.571 49.714-13.143 76-13.143 77.143 0 136.571 48.571 148.571 48.571 72.571 0 70.857-155.429 70.857-201.714zM1024 804.571c0-80.571-65.714-146.286-146.286-146.286s-146.286 65.714-146.286 146.286 65.714 146.286 146.286 146.286 146.286-65.714 146.286-146.286z" /> <glyph unicode="&#xe915;" glyph-name="local" horiz-adv-x="1097" d="M338.857 438.857c-59.429-1.714-113.143-27.429-151.429-73.143h-76.571c-57.143 0-110.857 27.429-110.857 90.857 0 46.286-1.714 201.714 70.857 201.714 12 0 71.429-48.571 148.571-48.571 26.286 0 51.429 4.571 76 13.143-1.714-12.571-2.857-25.143-2.857-37.714 0-52 16.571-103.429 46.286-146.286zM950.857 74.857c0-92.571-61.143-148-152.571-148h-499.429c-91.429 0-152.571 55.429-152.571 148 0 129.143 30.286 327.429 197.714 327.429 19.429 0 90.286-79.429 204.571-79.429s185.143 79.429 204.571 79.429c167.429 0 197.714-198.286 197.714-327.429zM365.714 804.571c0-80.571-65.714-146.286-146.286-146.286s-146.286 65.714-146.286 146.286 65.714 146.286 146.286 146.286 146.286-65.714 146.286-146.286zM768 585.143c0-121.143-98.286-219.429-219.429-219.429s-219.429 98.286-219.429 219.429 98.286 219.429 219.429 219.429 219.429-98.286 219.429-219.429zM1097.143 456.571c0-63.429-53.714-90.857-110.857-90.857h-76.571c-38.286 45.714-92 71.429-151.429 73.143 29.714 42.857 46.286 94.286 46.286 146.286 0 12.571-1.143 25.143-2.857 37.714 24.571-8.571 49.714-13.143 76-13.143 77.143 0 136.571 48.571 148.571 48.571 72.571 0 70.857-155.429 70.857-201.714zM1024 804.571c0-80.571-65.714-146.286-146.286-146.286s-146.286 65.714-146.286 146.286 65.714 146.286 146.286 146.286 146.286-65.714 146.286-146.286z" />
<glyph unicode="&#xe916;" glyph-name="dots-three" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM153.651 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64zM870.451 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" /> <glyph unicode="&#xe916;" glyph-name="dots-three" d="M512.051 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64zM153.651 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.483 112.589 112.64s-50.381 112.64-112.589 112.64zM870.451 573.44c-62.208 0-112.691-50.432-112.691-112.64s50.483-112.64 112.691-112.64c62.208 0 112.589 50.432 112.589 112.64s-50.381 112.64-112.589 112.64z" />
<glyph unicode="&#xe917;" glyph-name="check" d="M424.653 102.502c-22.272 0-43.366 10.394-56.883 28.314l-182.938 241.715c-23.808 31.386-17.613 76.083 13.824 99.891 31.488 23.91 76.186 17.613 99.994-13.824l120.371-158.925 302.643 485.99c20.838 33.382 64.87 43.622 98.355 22.784 33.434-20.787 43.725-64.819 22.835-98.304l-357.581-573.952c-12.39-20.019-33.843-32.512-57.344-33.587-1.126-0.102-2.15-0.102-3.277-0.102z" />
<glyph unicode="&#xe986;" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" /> <glyph unicode="&#xe986;" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
<glyph unicode="&#xe9d7;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" /> <glyph unicode="&#xe9d7;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9d8;" glyph-name="star-half" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-0.942-0.496 0.942 570.768 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" /> <glyph unicode="&#xe9d8;" glyph-name="star-half" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-0.942-0.496 0.942 570.768 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src: url('fonts/icomoon.eot?ouoizu'); src: url('fonts/icomoon.eot?aeb2zb');
src: url('fonts/icomoon.eot?ouoizu#iefix') format('embedded-opentype'), src: url('fonts/icomoon.eot?aeb2zb#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?ouoizu') format('truetype'), url('fonts/icomoon.ttf?aeb2zb') format('truetype'),
url('fonts/icomoon.woff?ouoizu') format('woff'), url('fonts/icomoon.woff?aeb2zb') format('woff'),
url('fonts/icomoon.svg?ouoizu#icomoon') format('svg'); url('fonts/icomoon.svg?aeb2zb#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-check:before {
content: "\e917";
}
.icon-dots-three:before { .icon-dots-three:before {
content: "\e916"; content: "\e916";
} }

View file

@ -1,6 +1,5 @@
''' Handle user activity ''' ''' Handle user activity '''
from datetime import datetime from datetime import datetime
from django.db import IntegrityError
from bookwyrm import activitypub, books_manager, models from bookwyrm import activitypub, books_manager, models
from bookwyrm.books_manager import get_or_create_book from bookwyrm.books_manager import get_or_create_book
@ -65,17 +64,6 @@ def create_generated_note(user, content, mention_books=None, privacy='public'):
return status return status
def create_tag(user, possible_book, name):
''' add a tag to a book '''
book = get_or_create_book(possible_book)
try:
tag = models.Tag.objects.create(name=name, book=book, user=user)
except IntegrityError:
return models.Tag.objects.get(name=name, book=book, user=user)
return tag
def create_notification(user, notification_type, related_user=None, \ def create_notification(user, notification_type, related_user=None, \
related_book=None, related_status=None, related_import=None): related_book=None, related_status=None, related_import=None):
''' let a user know when someone interacts with their content ''' ''' let a user know when someone interacts with their content '''

View file

@ -27,11 +27,18 @@
{% include 'snippets/shelve_button.html' %} {% include 'snippets/shelve_button.html' %}
{% if request.user.is_authenticated and not book.cover %} {% if request.user.is_authenticated and not book.cover %}
<div class="box p-2">
<form name="add-cover" method="POST" action="/upload_cover/{{ book.id }}" enctype="multipart/form-data"> <form name="add-cover" method="POST" action="/upload_cover/{{ book.id }}" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ cover_form.as_p }} <div class="field">
<button class="button" type="submit">Add cover</button> <label class="label" for="id_cover">Cover:</label>
<input type="file" name="cover" accept="image/*" class="" id="id_cover">
</div>
<div class="field">
<button class="button is-small" type="submit">Add cover</button>
</div>
</form> </form>
</div>
{% endif %} {% endif %}
<dl class="content"> <dl class="content">
@ -60,33 +67,34 @@
<input class="toggle-control" type="radio" name="show-edit-readthrough" id="show-readthrough-{{ readthrough.id }}" checked> <input class="toggle-control" type="radio" name="show-edit-readthrough" id="show-readthrough-{{ readthrough.id }}" checked>
<div class="toggle-content hidden"> <div class="toggle-content hidden">
<dl> <dl>
{% if readthrough.start_date %}
<dt>Started reading:</dt> <dt>Started reading:</dt>
<dd>{{ readthrough.start_date | naturalday }}</dd> <dd>{{ readthrough.start_date | naturalday }}</dd>
{% endif %}
{% if readthrough.finish_date %}
<dt>Finished reading:</dt> <dt>Finished reading:</dt>
<dd>{{ readthrough.finish_date | naturalday }}</dd> <dd>{{ readthrough.finish_date | naturalday }}</dd>
{% endif %}
</dl> </dl>
<div class="field is-grouped"> <div class="field is-grouped">
<label class="button is-small" for="edit-readthrough-{{ readthrough.id }}"> <label class="button is-small" for="edit-readthrough-{{ readthrough.id }}">
<span class="icon icon-pencil"> <span class="icon icon-pencil">
<span class="is-sr-only">Edit readthrough dates</span> <span class="is-sr-only">Edit read-through dates</span>
</span> </span>
</label> </label>
<form name="delete-readthrough-{{ readthrough.id }}" action="/delete-readthrough" method="POST"> <label class="button is-small" for="delete-readthrough-{{ readthrough.id }}">
{% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}">
<button class="button is-small" type="submit">
<span class="icon icon-x"> <span class="icon icon-x">
<span class="is-sr-only">Delete this readthrough</span> <span class="is-sr-only">Delete this read-through</span>
</span> </span>
</button> </label>
</form>
</div> </div>
</div> </div>
</div> </div>
<div class="content block"> <div class="block">
<input class="toggle-control" type="radio" name="show-edit-readthrough" id="edit-readthrough-{{ readthrough.id }}"> <input class="toggle-control" type="radio" name="show-edit-readthrough" id="edit-readthrough-{{ readthrough.id }}">
<div class="toggle-content hidden"> <div class="toggle-content hidden">
<div class="box">
<form name="edit-readthrough" action="/edit-readthrough" method="post"> <form name="edit-readthrough" action="/edit-readthrough" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}"> <input type="hidden" name="id" value="{{ readthrough.id }}">
@ -103,16 +111,41 @@
</label> </label>
</div> </div>
<div class="field is-grouped"> <div class="field is-grouped">
<button class="button is-small" type="submit">Save</button> <button class="button is-primary" type="submit">Save</button>
<label class="button is-small" for="show-readthrough-{{ readthrough.id }}">Cancel</label> <label class="button" for="show-readthrough-{{ readthrough.id }}">Cancel</label>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div>
<div>
<input class="toggle-control" type="checkbox" name="delete-readthrough-{{ readthrough.id }}" id="delete-readthrough-{{ readthrough.id }}">
<div class="modal toggle-content hidden">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Delete this read-though?</p>
<label class="delete" for="delete-readthrough-{{ readthrough.id }}" aria-label="close"></label>
</header>
<footer class="modal-card-foot">
<form name="delete-readthrough-{{ readthrough.id }}" action="/delete-readthrough" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}">
<button class="button is-danger" type="submit">
Delete
</button>
<label for="delete-readthrough-{{ readthrough.id }}" class="button">Cancel</button>
</form>
</footer>
</div>
<label class="modal-close is-large" for="delete-readthrough-{{ readthrough.id }}" aria-label="close"></label>
</div>
</div>
{% endfor %} {% endfor %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="block"> <div class="box">
{% include 'snippets/create_status.html' with book=book hide_cover=True %} {% include 'snippets/create_status.html' with book=book hide_cover=True %}
</div> </div>

View file

@ -1,7 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% load fr_display %} {% load fr_display %}
{% block content %} {% block content %}
{% include 'user_header.html' with user=user %} {% include 'snippets/user_header.html' with user=user %}
<div class="block"> <div class="block">
<h2 class="title">Followers</h2> <h2 class="title">Followers</h2>

View file

@ -1,7 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% load fr_display %} {% load fr_display %}
{% block content %} {% block content %}
{% include 'user_header.html' %} {% include 'snippets/user_header.html' with user=user %}
<div class="block"> <div class="block">
<h2 class="title">Following</h2> <h2 class="title">Following</h2>

View file

@ -57,6 +57,5 @@
</p> </p>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -29,23 +29,20 @@
<form name="invite" action="/create_invite/" method="post"> <form name="invite" action="/create_invite/" method="post">
{% csrf_token %} {% csrf_token %}
<div class="field"> <div class="field is-grouped">
<div class="control"> <div class="control">
<label class="label" for="id_expiry">Expiry:</label> <label class="label" for="id_expiry">Expiry:</label>
</div>
<div class="select"> <div class="select">
{{ form.expiry }} {{ form.expiry }}
</div> </div>
</div> </div>
<div class="field">
<div class="control"> <div class="control">
<label class="label" for="id_use_limit">Use limit:</label> <label class="label" for="id_use_limit">Use limit:</label>
</div>
<div class="select"> <div class="select">
{{ form.use_limit }} {{ form.use_limit }}
</div> </div>
</div> </div>
</div>
<button class="button is-primary" type="submit">Create Invite</button> <button class="button is-primary" type="submit">Create Invite</button>
</form> </form>

View file

@ -59,4 +59,3 @@
</div> </div>
{% endblock %} {% endblock %}

View file

@ -17,7 +17,7 @@
</div> </div>
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<button class="button is-primary" type="submit">Reset password</button> <button class="button is-link" type="submit">Reset password</button>
</div> </div>
</div> </div>
</form> </form>

View file

@ -49,7 +49,7 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="remote_id" value="{{ result.key }}"> <input type="hidden" name="remote_id" value="{{ result.key }}">
<div>{% include 'snippets/search_result_text.html' with result=result link=False %}</div> <div>{% include 'snippets/search_result_text.html' with result=result link=False %}</div>
<button type="submit" class="button is-small is-link is-light">Import book</button> <button type="submit" class="button is-small is-link">Import book</button>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}

View file

@ -1,7 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% load fr_display %} {% load fr_display %}
{% block content %} {% block content %}
{% include 'user_header.html' with user=user %} {% include 'snippets/user_header.html' with user=user %}
<div class="block"> <div class="block">
<div class="tabs"> <div class="tabs">

View file

@ -1,8 +1,6 @@
{% load humanize %} {% load humanize %}
{% load fr_display %} {% load fr_display %}
<div class="columns">
<div class="column">
<div class="tabs is-boxed"> <div class="tabs is-boxed">
<ul> <ul>
<li class="is-active" data-id="tab-review-{{ book.id }}" data-category="tab-option-{{ book.id }}"> <li class="is-active" data-id="tab-review-{{ book.id }}" data-category="tab-option-{{ book.id }}">
@ -19,91 +17,15 @@
<div> <div>
<input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="review-{{ book.id }}" checked> <input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="review-{{ book.id }}" checked>
<form class="toggle-content hidden tab-option-{{ book.id }}" name="review" action="/review/" method="post" id="tab-review-{{ book.id }}"> {% include 'snippets/create_status_form.html' with type='review' %}
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="user" value="{{ request.user.id }}">
<div class="control">
<label class="label" for="id_name_{{ book.id }}_review">Title:</label>
<input type="text" name="name" maxlength="255" class="input" required="" id="id_name_{{ book.id }}_review" placeholder="My review of '{{ book.title }}'">
</div>
<div class="control">
<label class="label" for="id_content_{{ book.id }}_review">Review:</label>
<span class="is-sr-only">Rating</span>
<div class="field is-grouped stars form-rate-stars">
<input class="hidden" type="radio" name="rating" value="" checked>
{% for i in '12345'|make_list %}
<input class="hidden" id="book{{book.id}}-star-{{ forloop.counter }}" type="radio" name="rating" value="{{ forloop.counter }}">
<label class="icon icon-star-empty" for="book{{book.id}}-star-{{ forloop.counter }}">
<span class="is-sr-only">{{ forloop.counter }} star{{ forloop.counter | pluralize }}</span>
</label>
{% endfor %}
</div>
<textarea name="content" class="textarea" id="id_content_{{ book.id }}_review"></textarea>
</div>
<div class="control is-grouped">
{% include 'snippets/privacy_select.html' %}
<button class="button is-primary" type="submit">post review</button>
</div>
</form>
</div> </div>
<div> <div>
<input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="comment-{{ book.id }}"> <input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="comment-{{ book.id }}">
<form class="toggle-content hidden tab-option-{{ book.id }}" name="comment" action="/comment/" method="post" id="tab-comment-{{ book.id }}"> {% include 'snippets/create_status_form.html' with type="comment" placeholder="Some thougts on '"|add:book.title|add:"'" %}
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="user" value="{{ request.user.id }}">
<input type="hidden" name="privacy" value="public">
<div class="control">
<label class="label" for="id_content_{{ book.id }}_comment">Comment:</label>
<textarea name="content" class="textarea" id="id_content_{{ book.id }}_comment" placeholder="Some thoughts on '{{ book.title }}'"></textarea>
</div>
<div class="control is-grouped">
<div class="select">
<select name="privacy">
<option value="public" selected>Public</option>
<option value="unlisted">Unlisted</option>
<option value="followers">Followers only</option>
<option value="direct">Private</option>
</select>
</div>
<button class="button is-primary" type="submit">post comment</button>
</div>
</form>
</div> </div>
<div> <div>
<input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="quote-{{ book.id }}"> <input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="quote-{{ book.id }}">
<form class="toggle-content hidden tab-option-{{ book.id }}" name="quotation" action="/quotate/" method="post" id="tab-quotation-{{ book.id }}"> {% include 'snippets/create_status_form.html' with type="quote" placeholder="An excerpt from '"|add:book.title|add:"'" %}
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="user" value="{{ request.user.id }}">
<input type="hidden" name="privacy" value="public">
<div class="control">
<label class="label" for="id_quote_{{ book.id }}_quote">Quote:</label>
<textarea name="quote" class="textarea" required="" id="id_quote_{{ book.id }}_quote" placeholder="An except from '{{ book.title }}'"></textarea>
</div>
<div class="control">
<label class="label" for="id_content_{{ book.id }}_quote">Comment:</label>
<textarea name="content" class="textarea is-small" id="id_content_{{ book.id }}_quote"></textarea>
</div>
<div class="control is-grouped">
<div class="select">
<select name="privacy">
<option value="public" selected>Public</option>
<option value="unlisted">Unlisted</option>
<option value="followers">Followers only</option>
<option value="direct">Private</option>
</select>
</div>
<button class="button is-primary" type="submit">post quote</button>
</div>
</form>
</div>
</div>
</div> </div>

View file

@ -0,0 +1,40 @@
<form class="toggle-content hidden tab-option-{{ book.id }}" name="{{ type }}" action="/{{ type }}" method="post" id="tab-{{ type }}-{{ book.id }}">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="user" value="{{ request.user.id }}">
{% if type == 'review' %}
<div class="control">
<label class="label" for="id_name_{{ book.id }}_{{ type }}">Title:</label>
<input type="text" name="name" maxlength="255" class="input" required="" id="id_name_{{ book.id }}_{{ type }}" placeholder="My {{ type }} of '{{ book.title }}'">
</div>
{% endif %}
<div class="control">
<label class="label" for="id_content_{{ book.id }}_{{ type }}">{{ type|title }}:</label>
{% if type == 'review' %}
<span class="is-sr-only">Rating</span>
<div class="field is-grouped stars form-rate-stars">
<input class="hidden" type="radio" name="rating" value="" checked>
{% for i in '12345'|make_list %}
<input class="hidden" id="book{{book.id}}-star-{{ forloop.counter }}" type="radio" name="rating" value="{{ forloop.counter }}">
<label class="icon icon-star-empty" for="book{{book.id}}-star-{{ forloop.counter }}">
<span class="is-sr-only">{{ forloop.counter }} star{{ forloop.counter | pluralize }}</span>
</label>
{% endfor %}
</div>
{% endif %}
<textarea name="{% if type == 'quote' %}quote{% else %}content{% endif %}" class="textarea" id="id_content_{{ book.id }}_{{ type }}" placeholder="{{ placeholder }}" required></textarea>
</div>
{% if type == 'quote' %}
<div class="control">
<label class="label" for="id_content_{{ book.id }}_quote">Comment:</label>
<textarea name="content" class="textarea is-small" id="id_content_{{ book.id }}_quote"></textarea>
</div>
{% endif %}
<div class="control is-grouped">
{% include 'snippets/privacy_select.html' %}
<button class="button is-primary" type="submit">post {{ type }}</button>
</div>
</form>

View file

@ -11,14 +11,14 @@ Follow request already sent.
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
{% if user.manually_approves_followers %} {% if user.manually_approves_followers %}
<button class="button is-small is-link is-light" type="submit">Send follow request</button> <button class="button is-small is-primary" type="submit">Send follow request</button>
{% else %} {% else %}
<button class="button is-small is-link is-light" type="submit">Follow</button> <button class="button is-small is-primary" type="submit">Follow</button>
{% endif %} {% endif %}
</form> </form>
<form action="/unfollow/" method="POST" onsubmit="interact(event)" class="follow-{{ user.id }} {% if not request.user in user.followers.all %}hidden{%endif %}" data-id="follow-{{ user.id }}"> <form action="/unfollow/" method="POST" onsubmit="interact(event)" class="follow-{{ user.id }} {% if not request.user in user.followers.all %}hidden{%endif %}" data-id="follow-{{ user.id }}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
<button class="button is-small is-danger is-light" type="submit">Unfollow</button> <button class="button is-small is-danger" type="submit">Unfollow</button>
</form> </form>
{% endif %} {% endif %}

View file

@ -3,11 +3,11 @@
<form action="/accept_follow_request/" method="POST"> <form action="/accept_follow_request/" method="POST">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
<button class="button is-small" type="submit">Accept</button> <button class="button is-primary is-small" type="submit">Accept</button>
</form> </form>
<form action="/delete_follow_request/" method="POST"> <form action="/delete_follow_request/" method="POST">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
<button class="button is-small" type="submit" class="warning">Delete</button> <button class="button is-danger is-small" type="submit" class="warning">Delete</button>
</form> </form>
{% endif %} {% endif %}

View file

@ -71,7 +71,7 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="shelf" value="{{ shelf.id }}"> <input type="hidden" name="shelf" value="{{ shelf.id }}">
<button class="button is-small" type="submit" style=""> <button class="button is-danger is-small" type="submit" style="">
<span class="icon icon-x"> <span class="icon icon-x">
<span class="is-sr-only">Remove from shelf</span> <span class="is-sr-only">Remove from shelf</span>
</span> </span>

View file

@ -5,7 +5,9 @@
{% active_shelf book as active_shelf %} {% active_shelf book as active_shelf %}
<div class="field is-grouped"> <div class="field is-grouped">
{% if active_shelf.identifier == 'read' %} {% if active_shelf.identifier == 'read' %}
<button class="button is-small" disabled>Read ✓</button> <button class="button is-small" disabled>
<span>Read</span> <span class="icon icon-check"></span>
</button>
{% elif active_shelf.identifier == 'reading' %} {% elif active_shelf.identifier == 'reading' %}
<label class="button is-small" for="finish-reading-{{ uuid }}"> <label class="button is-small" for="finish-reading-{{ uuid }}">
I'm done! I'm done!
@ -42,7 +44,10 @@
<form class="dropdown-item pt-0 pb-0" name="shelve" action="/shelve/" method="post"> <form class="dropdown-item pt-0 pb-0" name="shelve" action="/shelve/" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
<button class="button is-small" name="shelf" type="submit" value="{{ shelf.identifier }}" {% if shelf in book.shelf_set.all %} disabled {% endif %}>{{ shelf.name }} {% if shelf in book.shelf_set.all %} ✓ {% endif %}</button> <button class="button is-small" name="shelf" type="submit" value="{{ shelf.identifier }}" {% if shelf in book.shelf_set.all %} disabled {% endif %}>
<span>{{ shelf.name }}</span>
{% if shelf in book.shelf_set.all %}<span class="icon icon-check"></span>{% endif %}
</button>
</form> </form>
{% endif %} {% endif %}
</li> </li>
@ -58,7 +63,7 @@
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Started "{{ book.title }}"</p> <p class="modal-card-title">Start "{{ book.title }}"</p>
<label class="delete" for="start-reading-{{ uuid }}" aria-label="close"></label> <label class="delete" for="start-reading-{{ uuid }}" aria-label="close"></label>
</header> </header>
<form name="start-reading" action="/start-reading" method="post"> <form name="start-reading" action="/start-reading" method="post">
@ -82,14 +87,14 @@
{% include 'snippets/privacy_select.html' %} {% include 'snippets/privacy_select.html' %}
</div> </div>
<div class="column"> <div class="column">
<button type="submit" class="button is-success">Save</button> <button class="button is-success" type="submit">Save</button>
<label for="start-reading-{{ uuid }}" class="button">Cancel</button> <label for="start-reading-{{ uuid }}" class="button">Cancel</button>
</div> </div>
</div> </div>
</footer> </footer>
</form> </form>
</div> </div>
<label class="modal-close is-large" for="finish-reading-{{ uuid }}" aria-label="close"></label> <label class="modal-close is-large" for="start-reading-{{ uuid }}" aria-label="close"></label>
</div> </div>
</div> </div>
@ -99,7 +104,7 @@
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Finished "{{ book.title }}"</p> <p class="modal-card-title">Finish "{{ book.title }}"</p>
<label class="delete" for="finish-reading-{{ uuid }}" aria-label="close"></label> <label class="delete" for="finish-reading-{{ uuid }}" aria-label="close"></label>
</header> </header>
{% active_read_through book user as readthrough %} {% active_read_through book user as readthrough %}

View file

@ -5,18 +5,20 @@
<div class="card"> <div class="card">
<header class="card-header"> <header class="card-header">
<div class="card-header-title"> <div class="card-header-title">
<p> <div class="columns">
<div class="column is-narrow">
{% if status.status_type == 'Boost' %} {% if status.status_type == 'Boost' %}
{% include 'snippets/avatar.html' with user=status.user %} {% include 'snippets/avatar.html' with user=status.user %}
{% include 'snippets/username.html' with user=status.user %} {% include 'snippets/username.html' with user=status.user %}
boosted boosted
</p> </div>
<p> <div class="column">
{% include 'snippets/status_header.html' with status=status|boosted_status %} {% include 'snippets/status_header.html' with status=status|boosted_status %}
{% else %} {% else %}
{% include 'snippets/status_header.html' with status=status %} {% include 'snippets/status_header.html' with status=status %}
{% endif %} {% endif %}
</p> </div>
</div>
</div> </div>
</header> </header>
@ -117,7 +119,7 @@
<form name="delete-{{status.id}}" action="/delete-status" method="post"> <form name="delete-{{status.id}}" action="/delete-status" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="status" value="{{ status.id }}"> <input type="hidden" name="status" value="{{ status.id }}">
<button type="submit" class="button"> <button class="button is-danger" type="submit">
Delete post Delete post
</button> </button>
</form> </form>

View file

@ -1,24 +1,22 @@
<div class="control"> <div class="control">
<form name="tag" action="/{% if tag.identifier in user_tags %}untag{% else %}tag{% endif %}/" method="post">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="name" value="{{ tag.name }}">
<div class="tags has-addons"> <div class="tags has-addons">
<a class="tag is-link" href="/tag/{{ tag.identifier|urlencode }}"> <a class="tag" href="/tag/{{ tag.identifier|urlencode }}">
{{ tag.name }} {{ tag.name }}
</a> </a>
<div class="tag">
{% if tag.identifier in user_tags %} {% if tag.identifier in user_tags %}
<form name="tag" action="/untag/" method="post"> <button class="tag is-delete" type="submit">
{% csrf_token %} <span class="is-sr-only">remove tag</span>
<input type="hidden" name="book" value="{{ book.id }}"> </button>
<input type="hidden" name="name" value="{{ tag.name }}">
<button type="submit">x<span class="is-sr-only"> remove tag</span></button>
</form>
{% else %} {% else %}
<form name="tag" action="/tag/" method="post"> <button class="tag" type="submit">+
{% csrf_token %} <span class="is-sr-only">add tag</span>
<input type="hidden" name="book" value="{{ book.id }}"> </button>
<input type="hidden" name="name" value="{{ tag.name }}">
<button type="submit">+<span class="is-sr-only"> add tag</span></button>
</form>
{% endif %} {% endif %}
</div> </div>
</div> </form>
</div> </div>

View file

@ -18,15 +18,18 @@
<div class="column is-narrow"> <div class="column is-narrow">
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left">
<a href="/user/{{ user|username }}">
{% include 'snippets/avatar.html' with user=user large=True %} {% include 'snippets/avatar.html' with user=user large=True %}
</a>
</div> </div>
<div class="media-content"> <div class="media-content">
<p>{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}</p> <p>{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}</p>
<p>{{ user.username }}</p> <p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
<p>Joined {{ user.created_date | naturaltime }}</p> <p>Joined {{ user.created_date | naturaltime }}</p>
<p> <p>
<a href="/user/{{ user | username }}/followers">{{ user.followers.count }} follower{{ user.followers.count | pluralize }}</a>, <a href="/user/{{ user | username }}/followers">{{ user.followers.count }} follower{{ user.followers.count | pluralize }}</a>,
<a href="/user/{{ user | username }}/following">{{ user.following.count }} following</a></p> <a href="/user/{{ user | username }}/following">{{ user.following.count }} following</a>
</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,9 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block content %} {% block content %}
<div class="block"> {% include 'snippets/user_header.html' with user=user %}
{% include 'user_header.html' with user=user %}
</div>
<div class="block"> <div class="block">
<h2 class="title">Shelves</h2> <h2 class="title">Shelves</h2>

View file

@ -1,7 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% load fr_display %} {% load fr_display %}
{% block content %} {% block content %}
{% include 'user_header.html' with user=user %} {% include 'snippets/user_header.html' with user=user %}
<div class="block"> <div class="block">
<div class="tabs"> <div class="tabs">

View file

@ -75,7 +75,7 @@ urlpatterns = [
# books # books
re_path(r'%s(.json)?/?$' % book_path, views.book_page), re_path(r'%s(.json)?/?$' % book_path, views.book_page),
re_path(r'%s/edit/?$' % book_path, views.edit_book_page), re_path(r'%s/edit/?$' % book_path, views.edit_book_page),
re_path(r'%s/editions(.json)?/?' % book_path, views.editions_page), re_path(r'%s/editions(.json)?/?$' % book_path, views.editions_page),
re_path(r'^author/(?P<author_id>[\w\-]+)(.json)?/?$', views.author_page), re_path(r'^author/(?P<author_id>[\w\-]+)(.json)?/?$', views.author_page),
# TODO: tag needs a .json path # TODO: tag needs a .json path
@ -107,7 +107,7 @@ urlpatterns = [
re_path(r'^rate/?$', actions.rate), re_path(r'^rate/?$', actions.rate),
re_path(r'^review/?$', actions.review), re_path(r'^review/?$', actions.review),
re_path(r'^quotate/?$', actions.quotate), re_path(r'^quote/?$', actions.quotate),
re_path(r'^comment/?$', actions.comment), re_path(r'^comment/?$', actions.comment),
re_path(r'^tag/?$', actions.tag), re_path(r'^tag/?$', actions.tag),
re_path(r'^untag/?$', actions.untag), re_path(r'^untag/?$', actions.untag),

View file

@ -468,9 +468,18 @@ def tag(request):
# field which doesn't validate # field which doesn't validate
name = request.POST.get('name') name = request.POST.get('name')
book_id = request.POST.get('book') book_id = request.POST.get('book')
remote_id = 'https://%s/book/%s' % (DOMAIN, book_id) try:
book = models.Edition.objects.get(id=book_id)
except models.Edition.DoesNotExist:
return HttpResponseNotFound()
tag_obj, created = models.Tag.objects.get_or_create(
name=name,
book=book,
user=request.user
)
outgoing.handle_tag(request.user, remote_id, name) if created:
outgoing.handle_tag(request.user, tag_obj)
return redirect('/book/%s' % book_id) return redirect('/book/%s' % book_id)

View file

@ -430,7 +430,7 @@ def status_page(request, username, status_id):
return JsonResponse(status.to_activity(), encoder=ActivityEncoder) return JsonResponse(status.to_activity(), encoder=ActivityEncoder)
data = { data = {
'title': status.type, 'title': 'Status by %s' % user.username,
'status': status, 'status': status,
} }
return TemplateResponse(request, 'status.html', data) return TemplateResponse(request, 'status.html', data)