forked from mirrors/bookwyrm
Merge branch 'master' into edition-task
This commit is contained in:
commit
3a7c3724ad
28 changed files with 547 additions and 390 deletions
|
@ -19,18 +19,24 @@ def get_comment(comment):
|
||||||
status = get_status(comment)
|
status = get_status(comment)
|
||||||
status['inReplyToBook'] = comment.book.absolute_id
|
status['inReplyToBook'] = comment.book.absolute_id
|
||||||
status['fedireadsType'] = comment.status_type
|
status['fedireadsType'] = comment.status_type
|
||||||
status['name'] = comment.name
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
def get_review_article(review):
|
def get_review_article(review):
|
||||||
''' a book review formatted for a non-fedireads isntance (mastodon) '''
|
''' a book review formatted for a non-fedireads isntance (mastodon) '''
|
||||||
status = get_status(review)
|
status = get_status(review)
|
||||||
|
if review.rating:
|
||||||
name = 'Review of "%s" (%d stars): %s' % (
|
name = 'Review of "%s" (%d stars): %s' % (
|
||||||
review.book.title,
|
review.book.title,
|
||||||
review.rating,
|
review.rating,
|
||||||
review.name
|
review.name
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
name = 'Review of "%s": %s' % (
|
||||||
|
review.book.title,
|
||||||
|
review.name
|
||||||
|
)
|
||||||
|
|
||||||
status['name'] = name
|
status['name'] = name
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
@ -38,11 +44,8 @@ def get_review_article(review):
|
||||||
def get_comment_article(comment):
|
def get_comment_article(comment):
|
||||||
''' a book comment formatted for a non-fedireads isntance (mastodon) '''
|
''' a book comment formatted for a non-fedireads isntance (mastodon) '''
|
||||||
status = get_status(comment)
|
status = get_status(comment)
|
||||||
name = '%s (comment on "%s")' % (
|
status['content'] += '<br><br>(comment on <a href="%s">"%s"</a>)' % \
|
||||||
comment.name,
|
(comment.book.absolute_id, comment.book.title)
|
||||||
comment.book.title
|
|
||||||
)
|
|
||||||
status['name'] = name
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,8 @@ class Connector(AbstractConnector):
|
||||||
}
|
}
|
||||||
author = update_from_mappings(author, data, mappings)
|
author = update_from_mappings(author, data, mappings)
|
||||||
# TODO this is making some BOLD assumption
|
# TODO this is making some BOLD assumption
|
||||||
name = data['name']
|
name = data.get('name')
|
||||||
|
if name:
|
||||||
author.last_name = name.split(' ')[-1]
|
author.last_name = name.split(' ')[-1]
|
||||||
author.first_name = ' '.join(name.split(' ')[:-1])
|
author.first_name = ' '.join(name.split(' ')[:-1])
|
||||||
author.save()
|
author.save()
|
||||||
|
@ -223,8 +224,9 @@ def set_default_edition(work):
|
||||||
options = [e for e in options if e.cover] or options
|
options = [e for e in options if e.cover] or options
|
||||||
options = sorted(
|
options = sorted(
|
||||||
options,
|
options,
|
||||||
key=lambda e: e.published_date.year if e.published_date else None
|
key=lambda e: e.published_date.year if e.published_date else 3000
|
||||||
)
|
)
|
||||||
|
if len(options):
|
||||||
options[0].default = True
|
options[0].default = True
|
||||||
options[0].save()
|
options[0].save()
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,6 @@ class ReviewForm(ModelForm):
|
||||||
model = models.Review
|
model = models.Review
|
||||||
fields = ['name', 'rating', 'content']
|
fields = ['name', 'rating', 'content']
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
content = IntegerField(validators=[
|
|
||||||
MinValueValidator(0), MaxValueValidator(5)
|
|
||||||
])
|
|
||||||
labels = {
|
labels = {
|
||||||
'name': 'Title',
|
'name': 'Title',
|
||||||
'rating': 'Rating (out of 5)',
|
'rating': 'Rating (out of 5)',
|
||||||
|
@ -44,10 +41,9 @@ class ReviewForm(ModelForm):
|
||||||
class CommentForm(ModelForm):
|
class CommentForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Comment
|
model = models.Comment
|
||||||
fields = ['name', 'content']
|
fields = ['content']
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
labels = {
|
labels = {
|
||||||
'name': 'Title',
|
|
||||||
'content': 'Comment',
|
'content': 'Comment',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
fedireads/migrations/0028_auto_20200401_1824.py
Normal file
23
fedireads/migrations/0028_auto_20200401_1824.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-04-01 18:24
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fedireads', '0027_auto_20200330_2232'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='comment',
|
||||||
|
name='name',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='review',
|
||||||
|
name='rating',
|
||||||
|
field=models.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -48,12 +48,11 @@ class Status(FedireadsModel):
|
||||||
|
|
||||||
class Comment(Status):
|
class Comment(Status):
|
||||||
''' like a review but without a rating and transient '''
|
''' like a review but without a rating and transient '''
|
||||||
name = models.CharField(max_length=255)
|
|
||||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.status_type = 'Comment'
|
self.status_type = 'Comment'
|
||||||
self.activity_type = 'Article'
|
self.activity_type = 'Note'
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,8 +61,10 @@ class Review(Status):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||||
rating = models.IntegerField(
|
rating = models.IntegerField(
|
||||||
default=0,
|
default=None,
|
||||||
validators=[MinValueValidator(0), MaxValueValidator(5)]
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
validators=[MinValueValidator(1), MaxValueValidator(5)]
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -100,6 +101,7 @@ class Boost(Status):
|
||||||
self.status_type = 'Boost'
|
self.status_type = 'Boost'
|
||||||
self.activity_type = 'Announce'
|
self.activity_type = 'Announce'
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# This constraint can't work as it would cross tables.
|
# This constraint can't work as it would cross tables.
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# unique_together = ('user', 'boosted_status')
|
# unique_together = ('user', 'boosted_status')
|
||||||
|
|
|
@ -163,6 +163,10 @@ def handle_import_books(user, items):
|
||||||
identifier=item.shelf,
|
identifier=item.shelf,
|
||||||
user=user
|
user=user
|
||||||
)
|
)
|
||||||
|
if isinstance(item.book, models.Work):
|
||||||
|
item.book = item.book.default_edition
|
||||||
|
if not item.book:
|
||||||
|
continue
|
||||||
_, created = models.ShelfBook.objects.get_or_create(
|
_, created = models.ShelfBook.objects.get_or_create(
|
||||||
book=item.book, shelf=desired_shelf, added_by=user)
|
book=item.book, shelf=desired_shelf, added_by=user)
|
||||||
if created:
|
if created:
|
||||||
|
@ -201,10 +205,10 @@ def handle_review(user, book, name, content, rating):
|
||||||
broadcast(user, article_create_activity, other_recipients)
|
broadcast(user, article_create_activity, other_recipients)
|
||||||
|
|
||||||
|
|
||||||
def handle_comment(user, book, name, content):
|
def handle_comment(user, book, content):
|
||||||
''' post a review '''
|
''' post a review '''
|
||||||
# validated and saves the review in the database so it has an id
|
# validated and saves the review in the database so it has an id
|
||||||
comment = create_comment(user, book, name, content)
|
comment = create_comment(user, book, content)
|
||||||
|
|
||||||
comment_activity = activitypub.get_comment(comment)
|
comment_activity = activitypub.get_comment(comment)
|
||||||
comment_create_activity = activitypub.get_create(user, comment_activity)
|
comment_create_activity = activitypub.get_create(user, comment_activity)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from fedireads import models
|
||||||
def sync_book_data():
|
def sync_book_data():
|
||||||
''' update books with any changes to their canonical source '''
|
''' update books with any changes to their canonical source '''
|
||||||
expiry = timezone.now() - timedelta(days=1)
|
expiry = timezone.now() - timedelta(days=1)
|
||||||
books = models.Book.objects.filter(
|
books = models.Edition.objects.filter(
|
||||||
sync=True,
|
sync=True,
|
||||||
last_sync_date__lte=expiry
|
last_sync_date__lte=expiry
|
||||||
).all()
|
).all()
|
||||||
|
|
|
@ -1,57 +1,55 @@
|
||||||
/* some colors that are okay: #247BA0 #70C1B2 #B2DBBF #F3FFBD #FF1654 */
|
/* some colors that are okay: #247BA0 #70C1B2 #B2DBBF #F3FFBD #FF1654 */
|
||||||
|
|
||||||
|
/* general override */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1.3em;
|
line-height: 1.3em;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-top: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #247BA0;
|
color: #247BA0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, button {
|
|
||||||
padding: 0.2em 0.5em;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-weight: normal;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
font-weight: normal;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.5rem 0.2rem;
|
padding: 0.5rem 0.2rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-bottom: 3px solid #B2DBBF;
|
border-bottom: 3px solid #B2DBBF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 .edit-link {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9em;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
h2 .edit-link .icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 1rem 0 0.5rem 0;
|
|
||||||
border-bottom: 3px solid #70C1B2;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
h3 small {
|
h3 small {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* fixed display top bar */
|
||||||
|
body {
|
||||||
|
padding-top: 90px;
|
||||||
|
}
|
||||||
#top-bar {
|
#top-bar {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@ -65,18 +63,31 @@ h3 small {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#warning {
|
/* --- header bar content */
|
||||||
background-color: #FF1654;
|
#branding {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
#menu {
|
||||||
|
list-style: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
flex-grow: 2;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
#menu li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#menu a {
|
||||||
|
color: #555;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#branding a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#actions {
|
#actions {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions > * {
|
#actions > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +117,6 @@ h3 small {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
|
@ -116,11 +126,6 @@ h3 small {
|
||||||
background-color: #DDD;
|
background-color: #DDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
button .icon {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
vertical-align: sub;
|
|
||||||
}
|
|
||||||
#search button {
|
#search button {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -131,29 +136,6 @@ button .icon {
|
||||||
max-width: 55rem;
|
max-width: 55rem;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.menu {
|
|
||||||
list-style: none;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
ul.menu li {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: white;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
ul.menu a {
|
|
||||||
color: #555;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pulldown-container {
|
.pulldown-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -175,15 +157,24 @@ ul.menu a {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* content area */
|
||||||
|
.content-container {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
.content-container > * {
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
#feed {
|
#feed {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -50px;
|
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* row component */
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -204,6 +195,16 @@ ul.menu a {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.column > * {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* discover books page grid of covers */
|
||||||
.book-grid .book-cover {
|
.book-grid .book-cover {
|
||||||
height: 11em;
|
height: 11em;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
@ -212,6 +213,17 @@ ul.menu a {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* special case forms */
|
||||||
|
.review-form label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.review-form textarea {
|
||||||
|
width: 30rem;
|
||||||
|
height: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.follow-requests .row {
|
.follow-requests .row {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -219,6 +231,7 @@ ul.menu a {
|
||||||
width: 20em;
|
width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.login form {
|
.login form {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
@ -247,14 +260,14 @@ ul.menu a {
|
||||||
.book-form .row label {
|
.book-form .row label {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
form input {
|
|
||||||
flex-grow: 1;
|
/* general form stuff */
|
||||||
|
input, button {
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
}
|
}
|
||||||
form div {
|
button, input[type="submit"] {
|
||||||
margin-bottom: 1em;
|
cursor: pointer;
|
||||||
}
|
width: max-content;
|
||||||
textarea {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
}
|
||||||
.content-container button {
|
.content-container button {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -273,6 +286,36 @@ button.warning {
|
||||||
background-color: #FF1654;
|
background-color: #FF1654;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form input {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
form div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* icons */
|
||||||
|
a .icon {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
button .icon {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
vertical-align: sub;
|
||||||
|
}
|
||||||
|
.hidden-text {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* re-usable tab styles */
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -299,9 +342,10 @@ button.warning {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.user-pic {
|
.user-pic {
|
||||||
width: 2rem;
|
width: 2em;
|
||||||
height: 2rem;
|
height: 2em;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -312,51 +356,54 @@ button.warning {
|
||||||
height: 5em;
|
height: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 .edit-link {
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.9em;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
h2 .edit-link .icon {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
.user-profile .row > * {
|
.user-profile .row > * {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
.user-profile .row > *:last-child {
|
.user-profile .row > *:last-child {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
margin-left: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-form label {
|
/* general book display */
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-ago {
|
|
||||||
float: right;
|
|
||||||
display: block;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.book-preview {
|
.book-preview {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-preview img {
|
|
||||||
float: left;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.book-preview.grid {
|
.book-preview.grid {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-container {
|
.cover-container {
|
||||||
margin: 1rem;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
.content-container > * {
|
.cover-container button {
|
||||||
padding-left: 1em;
|
display: block;
|
||||||
padding-right: 1em;
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.book-cover {
|
||||||
|
width: 180px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.book-cover.small {
|
||||||
|
width: 50px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-cover {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.no-cover div {
|
||||||
|
position: absolute;
|
||||||
|
padding: 1em;
|
||||||
|
color: white;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.no-cover .title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.all-shelves {
|
.all-shelves {
|
||||||
|
@ -379,51 +426,32 @@ h2 .edit-link .icon {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-shelves .covers-shelf {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.user-shelves > div {
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.user-shelves > div > * {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
.user-shelves .covers-shelf .book-cover {
|
|
||||||
height: 9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.covers-shelf {
|
.covers-shelf {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
.covers-shelf .book-preview {
|
.covers-shelf .cover-container {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
width: min-content;
|
width: min-content;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.covers-shelf .book-preview button {
|
.covers-shelf .cover-container:last-child {
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.covers-shelf .book-preview:last-child {
|
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
.covers-shelf .book-preview:hover {
|
.covers-shelf img:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
.covers-shelf .book-preview:hover img {
|
|
||||||
box-shadow: #F3FFBD 0em 0em 1em 1em;
|
box-shadow: #F3FFBD 0em 0em 1em 1em;
|
||||||
}
|
}
|
||||||
.covers-shelf .book-cover {
|
.covers-shelf .book-cover {
|
||||||
float: none;
|
|
||||||
height: 11rem;
|
height: 11rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.covers-shelf button {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -437,29 +465,6 @@ h2 .edit-link .icon {
|
||||||
.compose-suggestion.visible {
|
.compose-suggestion.visible {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-cover {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.no-cover div {
|
|
||||||
position: absolute;
|
|
||||||
padding: 1em;
|
|
||||||
color: white;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.no-cover .title {
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
.book-cover {
|
|
||||||
width: 180px;
|
|
||||||
}
|
|
||||||
.book-cover.small {
|
|
||||||
width: 50px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.compose-suggestion .book-preview {
|
.compose-suggestion .book-preview {
|
||||||
background-color: #EEE;
|
background-color: #EEE;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
@ -476,14 +481,8 @@ h2 .edit-link .icon {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-form textarea {
|
|
||||||
width: 30rem;
|
|
||||||
height: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
margin-left: 2em;
|
|
||||||
}
|
}
|
||||||
blockquote .icon-quote-open {
|
blockquote .icon-quote-open {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -557,8 +556,11 @@ th, td {
|
||||||
color: #FF1654;
|
color: #FF1654;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-thread .reply h2 {
|
/* status css */
|
||||||
background: none;
|
.time-ago {
|
||||||
|
float: right;
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.post {
|
.post {
|
||||||
background-color: #EFEFEF;
|
background-color: #EFEFEF;
|
||||||
|
@ -577,7 +579,18 @@ th, td {
|
||||||
.post .user-pic, .compose-suggestion .user-pic {
|
.post .user-pic, .compose-suggestion .user-pic {
|
||||||
right: 0.25em;
|
right: 0.25em;
|
||||||
}
|
}
|
||||||
|
.post h2 .subhead {
|
||||||
|
display: block;
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
.post .subhead .time-ago {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* status page with replies */
|
||||||
|
.comment-thread .reply h2 {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
.comment-thread .post {
|
.comment-thread .post {
|
||||||
margin-left: 4em;
|
margin-left: 4em;
|
||||||
border-left: 2px solid #247BA0;
|
border-left: 2px solid #247BA0;
|
||||||
|
@ -596,14 +609,16 @@ th, td {
|
||||||
margin-left: 3em;
|
margin-left: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
a .icon {
|
/* pagination */
|
||||||
color: black;
|
.pagination a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.pagination .next {
|
||||||
.hidden-text {
|
text-align: right;
|
||||||
height: 0;
|
}
|
||||||
width: 0;
|
|
||||||
position: absolute;
|
/* special one-off "delete all data" banner */
|
||||||
overflow: hidden;
|
#warning {
|
||||||
|
background-color: #FF1654;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
function show_compose(element) {
|
|
||||||
var visible_compose_boxes = document.getElementsByClassName('visible');
|
|
||||||
for (var i = 0; i < visible_compose_boxes.length; i++) {
|
|
||||||
visible_compose_boxes[i].className = 'compose-suggestion';
|
|
||||||
}
|
|
||||||
|
|
||||||
var target_id = 'compose-' + element.id;
|
|
||||||
var target = document.getElementById(target_id);
|
|
||||||
target.className += ' visible';
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
function show_compose(element, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var visible_compose_boxes = document.getElementsByClassName('visible');
|
||||||
|
for (var i = 0; i < visible_compose_boxes.length; i++) {
|
||||||
|
visible_compose_boxes[i].className = 'compose-suggestion';
|
||||||
|
}
|
||||||
|
|
||||||
|
var target_id = 'compose-' + element.id;
|
||||||
|
var target = document.getElementById(target_id);
|
||||||
|
target.className += ' visible';
|
||||||
|
}
|
||||||
|
|
||||||
function hide_element(element) {
|
function hide_element(element) {
|
||||||
var classes = element.parentElement.className;
|
var classes = element.parentElement.className;
|
||||||
element.parentElement.className = classes.replace('visible', '');
|
element.parentElement.className = classes.replace('visible', '');
|
||||||
|
|
|
@ -31,7 +31,11 @@ def create_review(user, possible_book, name, content, rating):
|
||||||
content = sanitize(content)
|
content = sanitize(content)
|
||||||
|
|
||||||
# no ratings outside of 0-5
|
# no ratings outside of 0-5
|
||||||
rating = rating if 0 <= rating <= 5 else 0
|
try:
|
||||||
|
rating = int(rating)
|
||||||
|
rating = rating if 1 <= rating <= 5 else None
|
||||||
|
except ValueError:
|
||||||
|
rating = None
|
||||||
|
|
||||||
return models.Review.objects.create(
|
return models.Review.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
|
@ -46,19 +50,18 @@ def create_comment_from_activity(author, activity):
|
||||||
''' parse an activity json blob into a status '''
|
''' parse an activity json blob into a status '''
|
||||||
book = activity['inReplyToBook']
|
book = activity['inReplyToBook']
|
||||||
book = book.split('/')[-1]
|
book = book.split('/')[-1]
|
||||||
name = activity.get('name')
|
|
||||||
content = activity.get('content')
|
content = activity.get('content')
|
||||||
published = activity.get('published')
|
published = activity.get('published')
|
||||||
remote_id = activity['id']
|
remote_id = activity['id']
|
||||||
|
|
||||||
comment = create_comment(author, book, name, content)
|
comment = create_comment(author, book, content)
|
||||||
comment.published_date = published
|
comment.published_date = published
|
||||||
comment.remote_id = remote_id
|
comment.remote_id = remote_id
|
||||||
comment.save()
|
comment.save()
|
||||||
return comment
|
return comment
|
||||||
|
|
||||||
|
|
||||||
def create_comment(user, possible_book, name, content):
|
def create_comment(user, possible_book, content):
|
||||||
''' a book comment has been added '''
|
''' a book comment has been added '''
|
||||||
# throws a value error if the book is not found
|
# throws a value error if the book is not found
|
||||||
book = get_or_create_book(possible_book)
|
book = get_or_create_book(possible_book)
|
||||||
|
@ -67,7 +70,6 @@ def create_comment(user, possible_book, name, content):
|
||||||
return models.Comment.objects.create(
|
return models.Comment.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
book=book,
|
book=book,
|
||||||
name=name,
|
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,16 @@
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<h2>Books by {{ author.name }}</h2>
|
<h2>Books by {{ author.name }}</h2>
|
||||||
|
<div class="book-grid row shrink wrap">
|
||||||
{% for book in books %}
|
{% for book in books %}
|
||||||
<div class="book-preview">
|
<div class="book-preview">
|
||||||
{% include 'snippets/book.html' with book=book size=large description=True %}
|
<a href="{{ book.absolute_id }}">
|
||||||
|
{% include 'snippets/book_cover.html' with book=book %}
|
||||||
|
</a>
|
||||||
|
{% include 'snippets/shelve_button.html' with book=book %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load fr_display %}
|
{% load fr_display %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="content-container user-profile">
|
<div class="content-container">
|
||||||
<h2><q>{{ book.title }}</q> by
|
<h2>
|
||||||
{% include 'snippets/authors.html' with book=book %}
|
{% include 'snippets/book_titleby.html' with book=book %}
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<a href="{{ book.fedireads_key }}/edit" class="edit-link">edit
|
<a href="{{ book.fedireads_key }}/edit" class="edit-link">edit
|
||||||
|
@ -13,27 +13,14 @@
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
|
||||||
<div class="book-preview">
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="cover-container">
|
||||||
{% include 'snippets/book_cover.html' with book=book size=large %}
|
{% include 'snippets/book_cover.html' with book=book size=large %}
|
||||||
<p>{{ active_tab }} rating: {{ rating | stars }}</p>
|
|
||||||
{% if book.parent_work.description %}
|
|
||||||
<blockquote>{{ book.parent_work.description | description }}</blockquote>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<div id="tag-cloud">
|
|
||||||
{% for tag in tags %}
|
|
||||||
{% include 'snippets/tag.html' with tag=tag user=request.user user_tags=user_tag_names %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p><a href="/editions/{{ book.parent_work.id }}">{{ book.parent_work.edition_set.count }} other editions</a></p>
|
|
||||||
|
|
||||||
{% include 'snippets/shelve_button.html' %}
|
{% include 'snippets/shelve_button.html' %}
|
||||||
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{% if request.user.is_authenticated and not book.cover %}
|
{% if request.user.is_authenticated and not book.cover %}
|
||||||
<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 %}
|
||||||
|
@ -41,21 +28,48 @@
|
||||||
<button type="submit">Add cover</button>
|
<button type="submit">Add cover</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
<dl>
|
||||||
<div class="content-container">
|
{% for field in info_fields %}
|
||||||
<h2>Leave a review</h2>
|
{% if field.value %}
|
||||||
|
<dt>{{ field.name }}:</dt>
|
||||||
|
<dd>{{ field.value }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<h3>{{ active_tab }} rating: {{ rating | stars }}</h3>
|
||||||
|
|
||||||
|
{% include 'snippets/book_description.html' %}
|
||||||
|
|
||||||
|
{% if book.parent_work.edition_set.count > 1 %}
|
||||||
|
<p><a href="/editions/{{ book.parent_work.id }}">{{ book.parent_work.edition_set.count }} editions</a></p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="tag-cloud">
|
||||||
|
{% for tag in tags %}
|
||||||
|
{% include 'snippets/tag.html' with tag=tag user=request.user user_tags=user_tag_names %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<h3>Leave a review</h3>
|
||||||
<form class="review-form" name="review" action="/review/" method="post">
|
<form class="review-form" name="review" action="/review/" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="book" value="{{ book.fedireads_key }}"></input>
|
<input type="hidden" name="book" value="{{ book.fedireads_key }}"></input>
|
||||||
{{ review_form.as_p }}
|
{{ review_form.as_p }}
|
||||||
<button type="submit">Post review</button>
|
<button type="submit">Post review</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
<div class="content-container tabs">
|
<div class="content-container tabs">
|
||||||
{% include 'snippets/tabs.html' with tabs=feed_tabs active_tab=active_tab path=path %}
|
{% include 'snippets/tabs.html' with tabs=feed_tabs active_tab=active_tab path=path %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,22 +2,8 @@
|
||||||
{% load fr_display %}
|
{% load fr_display %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="all-shelves content-container">
|
{% include 'snippets/covers_shelf.html' with shelves=shelves user=request.user %}
|
||||||
{% include 'snippets/covers_shelf.html' with shelves=shelves user=request.user %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for shelf in shelves %}
|
|
||||||
{% for book in shelf.books %}
|
|
||||||
<div class="compose-suggestion" id="compose-book-{{ book.id }}">
|
|
||||||
<span class="close icon icon-close" onclick="hide_element(this)">
|
|
||||||
<span class="hidden-text">Close</span>
|
|
||||||
</span>
|
|
||||||
<div class="content-container">
|
|
||||||
{% include 'snippets/create_status.html' with book=book user=request.user %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div id="feed">
|
<div id="feed">
|
||||||
<div class="content-container tabs">
|
<div class="content-container tabs">
|
||||||
|
@ -29,7 +15,26 @@
|
||||||
{% include 'snippets/status.html' with status=activity %}
|
{% include 'snippets/status.html' with status=activity %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="content-container pagination row">
|
||||||
|
{% if prev %}
|
||||||
|
<p>
|
||||||
|
<a href="{{ prev }}">
|
||||||
|
<span class="icon icon-arrow-left"></span>
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if next %}
|
||||||
|
<p class="next">
|
||||||
|
<a href="{{ next }}">
|
||||||
|
Next
|
||||||
|
<span class="icon icon-arrow-right"></span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/feed.js"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -22,10 +22,14 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="top-bar">
|
<div id="top-bar">
|
||||||
<header>
|
<header class="row">
|
||||||
<div id="branding"><a href="/"><img id="logo" src="/static/images/logo-small.png" alt="BookWyrm"></img></a></div>
|
<div id="branding">
|
||||||
|
<a href="/">
|
||||||
|
<img id="logo" src="/static/images/logo-small.png" alt="BookWyrm"></img>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="menu">
|
<ul id="menu">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<li><a href="/user/{{request.user.localname}}/shelves">Your shelves</a></li>
|
<li><a href="/user/{{request.user.localname}}/shelves">Your shelves</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% load fr_display %}
|
|
||||||
{% include 'snippets/book_cover.html' with book=book %}
|
|
||||||
<p class="title">
|
|
||||||
<a href="/book/{{ book.fedireads_key }}">{{ book.title }}</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
by {% include 'snippets/authors.html' with book=book %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if rating %}
|
|
||||||
{{ rating | stars }} {{ rating }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if description %}
|
|
||||||
<blockquote>{{ book.description | description }}</blockquote>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include 'snippets/shelve_button.html' with book=book pulldown=shelf_pulldown %}
|
|
7
fedireads/templates/snippets/book_description.html
Normal file
7
fedireads/templates/snippets/book_description.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% load fr_display %}
|
||||||
|
{% if book.description %}
|
||||||
|
<blockquote>{{ book.description | description }}</blockquote>
|
||||||
|
{% elif book.parent_work.description %}
|
||||||
|
<blockquote>{{ book.parent_work.description | description }}</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
|
9
fedireads/templates/snippets/book_titleby.html
Normal file
9
fedireads/templates/snippets/book_titleby.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<span class="title">
|
||||||
|
<a href="/book/{{ book.fedireads_key }}">{{ book.title }}</a>
|
||||||
|
</span>
|
||||||
|
{% if book.authors %}
|
||||||
|
<span class="author">
|
||||||
|
by {% include 'snippets/authors.html' with book=book %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -1,20 +1,40 @@
|
||||||
{% load fr_display %}
|
{% load fr_display %}
|
||||||
{% for shelf in shelves %}
|
|
||||||
{% if shelf.books %}
|
<div class="all-shelves content-container">
|
||||||
<div>
|
{% for shelf in shelves %}
|
||||||
|
{% if shelf.books %}
|
||||||
|
<div>
|
||||||
<h2>{{ shelf.name }}
|
<h2>{{ shelf.name }}
|
||||||
{% if shelf.size > shelf.books|length %}
|
{% if shelf.size > shelf.books|length %}
|
||||||
<small>(<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>)</small>
|
<small>(<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>)</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="covers-shelf {{ shelf.identifier }}">
|
<div class="covers-shelf {{ shelf.identifier }} ">
|
||||||
{% for book in shelf.books %}
|
{% for book in shelf.books %}
|
||||||
<div class="book-preview" onclick="show_compose(this)" id="book-{{ book.id }}">
|
<div class="cover-container">
|
||||||
|
<div >
|
||||||
|
<a href="{{ book.absolute_id }}" onclick="show_compose(this, event)" id="book-{{ book.id }}">
|
||||||
{% include 'snippets/book_cover.html' with book=book %}
|
{% include 'snippets/book_cover.html' with book=book %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{% include 'snippets/shelve_button.html' with book=book %}
|
{% include 'snippets/shelve_button.html' with book=book %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
{% for shelf in shelves %}
|
||||||
|
{% for book in shelf.books %}
|
||||||
|
<div class="compose-suggestion" id="compose-book-{{ book.id }}">
|
||||||
|
<span class="close icon icon-close" onclick="hide_element(this)">
|
||||||
|
<span class="hidden-text">Close</span>
|
||||||
|
</span>
|
||||||
|
<div class="content-container">
|
||||||
|
{% include 'snippets/create_status.html' with book=book user=request.user %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form name="boost" action="/boost/{{ activity.id }}" method="post" onsubmit="return interact(event)" class="boost-{{ status.id }} {% if False %}hidden{% endif %}" data-id="boost-{{ status.id }}">
|
<form name="boost" action="/boost/{{ activity.id }}" method="post" onsubmit="return interact(event)" class="boost-{{ status.id }} {% if request.user|boosted:status %}hidden{% endif %}" data-id="boost-{{ status.id }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<span class="icon icon-boost">
|
<span class="icon icon-boost">
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form name="unboost" action="/unboost/{{ activity.id }}" method="post" onsubmit="return interact(event)" class="boost-{{ status.id }} active {% if not False %}hidden{% endif %}" data-id="boost-{{ status.id }}">
|
<form name="unboost" action="/unboost/{{ activity.id }}" method="post" onsubmit="return interact(event)" class="boost-{{ status.id }} active {% if not request.user|boosted:status %}hidden{% endif %}" data-id="boost-{{ status.id }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<span class="icon icon-boost">
|
<span class="icon icon-boost">
|
||||||
|
|
|
@ -1,59 +1,24 @@
|
||||||
{% load humanize %}
|
|
||||||
{% load fr_display %}
|
{% load fr_display %}
|
||||||
<div class="post {{ status.status_type|lower }} depth-{{ depth }} {% if main %}main{% else %}reply{% endif %}">
|
|
||||||
|
<div class="post {{ status.status_type | lower }} depth-{{ depth }} {% if main %}main{% else %}reply{% endif %}">
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
{% include 'snippets/avatar.html' with user=status.user %}
|
{% if status.boosted_status %}
|
||||||
{% include 'snippets/username.html' with user=status.user %}
|
{% include 'snippets/status_header.html' with status=status.boosted_status %}
|
||||||
{% if status.status_type == 'Update' %}
|
<small class="subhead">{% include 'snippets/status_header.html' with status=status %}</small>
|
||||||
{{ status.content | safe }}
|
{% else %}
|
||||||
{% elif status.status_type == 'Review' %}
|
{% include 'snippets/status_header.html' with status=status %}
|
||||||
reviewed {{ status.book.title }}
|
|
||||||
{% elif status.status_type == 'Comment' %}
|
|
||||||
commented on {{ status.book.title }}
|
|
||||||
{% elif status.status_type == 'Boost' %}
|
|
||||||
boosted
|
|
||||||
{% elif status.reply_parent %}
|
|
||||||
{% with parent_status=status|parent %}
|
|
||||||
replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} <a href="{{parent_status.absolute_id }}">{{ parent_status.status_type|lower }}</a>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="time-ago">
|
|
||||||
<a href="{{ status.absolute_id }}">{{ status.published_date | naturaltime }}</a>
|
|
||||||
</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if not hide_book and status.mention_books.count %}
|
<div class="status-content">
|
||||||
{% for book in status.mention_books.all|slice:"0:3" %}
|
{% include 'snippets/status_content.html' with status=status %}
|
||||||
<div class="book-preview">
|
|
||||||
{% if status.status_type == 'Review' %}
|
|
||||||
{% include 'snippets/book.html' with book=book %}
|
|
||||||
{% else %}
|
|
||||||
{% include 'snippets/book.html' with book=book description=True %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if not hide_book and status.book%}
|
|
||||||
<div class="book-preview">
|
|
||||||
{% if status.status_type == 'Review' %}
|
|
||||||
{% include 'snippets/book.html' with book=status.book %}
|
|
||||||
{% else %}
|
|
||||||
{% include 'snippets/book.html' with book=status.book description=True %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if status.status_type == 'Review' %}<h4>{{ status.name }}
|
|
||||||
<small>{{ status.rating | stars }} stars, by {% include 'snippets/username.html' with user=status.user %}</small>
|
|
||||||
</h4>{% endif %}
|
|
||||||
{% if status.status_type != 'Update' and status.status_type != 'Boost' %}
|
|
||||||
<blockquote>{{ status.content | safe }}</blockquote>
|
|
||||||
{% endif %}
|
|
||||||
{% if status.status_type == 'Boost' %}
|
|
||||||
{% include 'snippets/status.html' with status=status.boosted_status depth=depth|add:1 %}
|
|
||||||
{% endif %}
|
|
||||||
{% if not max_depth and status.reply_parent or status|replies %}<p><a href="{{ status.absolute_id }}">Thread</a>{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if status.status_type != 'Boost' %}
|
{% if status.status_type == 'Boost' %}
|
||||||
|
{% include 'snippets/interaction.html' with activity=status|boosted_status %}
|
||||||
|
{% else %}
|
||||||
{% include 'snippets/interaction.html' with activity=status %}
|
{% include 'snippets/interaction.html' with activity=status %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
52
fedireads/templates/snippets/status_content.html
Normal file
52
fedireads/templates/snippets/status_content.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{% load fr_display %}
|
||||||
|
|
||||||
|
{% if not hide_book and status.mention_books.count %}
|
||||||
|
<div class="row">
|
||||||
|
{% for book in status.mention_books.all|slice:"0:4" %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="cover-container">
|
||||||
|
{% include 'snippets/book_cover.html' with book=book %}
|
||||||
|
{% if status.mention_books.count > 1 %}
|
||||||
|
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% include 'snippets/shelve_button.html' with book=book %}
|
||||||
|
</div>
|
||||||
|
{% if status.mention_books.count == 1 %}
|
||||||
|
<div>
|
||||||
|
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
|
||||||
|
{% include 'snippets/book_description.html' with book=book %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% if not hide_book and status.book %}
|
||||||
|
<div class="cover-container">
|
||||||
|
{% include 'snippets/book_cover.html' with book=status.book %}
|
||||||
|
{% include 'snippets/shelve_button.html' with book=status.book %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if status.status_type == 'Review' %}
|
||||||
|
<h3>
|
||||||
|
{{ status.name }}<br>
|
||||||
|
{{ status.rating | stars }}
|
||||||
|
</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if status.status_type != 'Update' and status.status_type != 'Boost' %}
|
||||||
|
<blockquote>{{ status.content | safe }}</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if status.status_type == 'Boost' %}
|
||||||
|
{% include 'snippets/status_content.html' with status=status|boosted_status %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not max_depth and status.reply_parent or status|replies %}<p><a href="{{ status.absolute_id }}">Thread</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
21
fedireads/templates/snippets/status_header.html
Normal file
21
fedireads/templates/snippets/status_header.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% load humanize %}
|
||||||
|
{% load fr_display %}
|
||||||
|
{% include 'snippets/avatar.html' with user=status.user %}
|
||||||
|
{% include 'snippets/username.html' with user=status.user %}
|
||||||
|
|
||||||
|
{% if status.status_type == 'Update' %}
|
||||||
|
{{ status.content | safe }}
|
||||||
|
{% elif status.status_type == 'Review' %}
|
||||||
|
reviewed {{ status.book.title }}
|
||||||
|
{% elif status.status_type == 'Comment' %}
|
||||||
|
commented on {{ status.book.title }}
|
||||||
|
{% elif status.status_type == 'Boost' %}
|
||||||
|
boosted
|
||||||
|
{% elif status.reply_parent %}
|
||||||
|
{% with parent_status=status|parent %}
|
||||||
|
replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} <a href="{{parent_status.absolute_id }}">{{ parent_status.status_type | lower }}</a>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
<span class="time-ago">
|
||||||
|
<a href="{{ status.absolute_id }}">{{ status.published_date | naturaltime }}</a>
|
||||||
|
</span>
|
|
@ -2,9 +2,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% include 'user_header.html' with user=user %}
|
{% include 'user_header.html' with user=user %}
|
||||||
<div class="all-shelves content-container">
|
{% include 'snippets/covers_shelf.html' with shelves=shelves user=user %}
|
||||||
{% include 'snippets/covers_shelf.html' with shelves=shelves user=user %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="content-container"><h2>User Activity</h2></div>
|
<div class="content-container"><h2>User Activity</h2></div>
|
||||||
|
|
|
@ -88,6 +88,12 @@ def get_user_liked(user, status):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name='boosted')
|
||||||
|
def get_user_boosted(user, status):
|
||||||
|
''' did the given user fav a status? '''
|
||||||
|
return user.id in status.boosters.all().values_list('user', flat=True)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='follow_request_exists')
|
@register.filter(name='follow_request_exists')
|
||||||
def follow_request_exists(user, requester):
|
def follow_request_exists(user, requester):
|
||||||
''' see if there is a pending follow request for a user '''
|
''' see if there is a pending follow request for a user '''
|
||||||
|
@ -101,6 +107,13 @@ def follow_request_exists(user, requester):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name='boosted_status')
|
||||||
|
def get_boosted(boost):
|
||||||
|
''' load a boosted status. have to do this or it wont get foregin keys '''
|
||||||
|
return models.Status.objects.select_subclasses().filter(
|
||||||
|
id=boost.boosted_status.id
|
||||||
|
).get()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def shelve_button_identifier(context, book):
|
def shelve_button_identifier(context, book):
|
||||||
|
|
|
@ -170,7 +170,7 @@ def review(request):
|
||||||
# TODO: validation, htmlification
|
# TODO: validation, htmlification
|
||||||
name = form.data.get('name')
|
name = form.data.get('name')
|
||||||
content = form.data.get('content')
|
content = form.data.get('content')
|
||||||
rating = int(form.data.get('rating'))
|
rating = form.data.get('rating')
|
||||||
|
|
||||||
outgoing.handle_review(request.user, book_identifier, name, content, rating)
|
outgoing.handle_review(request.user, book_identifier, name, content, rating)
|
||||||
return redirect('/book/%s' % book_identifier)
|
return redirect('/book/%s' % book_identifier)
|
||||||
|
@ -186,10 +186,9 @@ def comment(request):
|
||||||
return redirect('/book/%s' % book_identifier)
|
return redirect('/book/%s' % book_identifier)
|
||||||
|
|
||||||
# TODO: validation, htmlification
|
# TODO: validation, htmlification
|
||||||
name = form.data.get('name')
|
|
||||||
content = form.data.get('content')
|
content = form.data.get('content')
|
||||||
|
|
||||||
outgoing.handle_comment(request.user, book_identifier, name, content)
|
outgoing.handle_comment(request.user, book_identifier, content)
|
||||||
return redirect('/book/%s' % book_identifier)
|
return redirect('/book/%s' % book_identifier)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,12 @@ def home(request):
|
||||||
@login_required
|
@login_required
|
||||||
def home_tab(request, tab):
|
def home_tab(request, tab):
|
||||||
''' user's homepage with activity feed '''
|
''' user's homepage with activity feed '''
|
||||||
|
page_size = 15
|
||||||
|
try:
|
||||||
|
page = int(request.GET.get('page', 1))
|
||||||
|
except ValueError:
|
||||||
|
page = 1
|
||||||
|
|
||||||
shelves = []
|
shelves = []
|
||||||
shelves = get_user_shelf_preview(
|
shelves = get_user_shelf_preview(
|
||||||
request.user,
|
request.user,
|
||||||
|
@ -67,30 +73,13 @@ def home_tab(request, tab):
|
||||||
# allows us to check if a user has shelved a book
|
# allows us to check if a user has shelved a book
|
||||||
user_books = models.Edition.objects.filter(shelves__user=request.user).all()
|
user_books = models.Edition.objects.filter(shelves__user=request.user).all()
|
||||||
|
|
||||||
# status updates for your follow network
|
activities = get_activity_feed(request.user, tab)
|
||||||
following = models.User.objects.filter(
|
|
||||||
Q(followers=request.user) | Q(id=request.user.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
activities = models.Status.objects.order_by(
|
activity_count = activities.count()
|
||||||
'-created_date'
|
activities = activities[(page - 1) * page_size:page * page_size]
|
||||||
).select_subclasses()
|
|
||||||
|
|
||||||
if tab == 'home':
|
|
||||||
# people you follow and direct mentions
|
|
||||||
activities = activities.filter(
|
|
||||||
Q(user__in=following, privacy='public') | \
|
|
||||||
Q(mention_users=request.user)
|
|
||||||
)
|
|
||||||
elif tab == 'local':
|
|
||||||
# everyone on this instance
|
|
||||||
activities = activities.filter(user__local=True, privacy='public')
|
|
||||||
else:
|
|
||||||
# all activities from everyone you federate with
|
|
||||||
activities = activities.filter(privacy='public')
|
|
||||||
|
|
||||||
activities = activities[:10]
|
|
||||||
|
|
||||||
|
next_page = '/?page=%d' % (page + 1)
|
||||||
|
prev_page = '/?page=%d' % (page - 1)
|
||||||
data = {
|
data = {
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
'shelves': shelves,
|
'shelves': shelves,
|
||||||
|
@ -104,10 +93,48 @@ def home_tab(request, tab):
|
||||||
'active_tab': tab,
|
'active_tab': tab,
|
||||||
'review_form': forms.ReviewForm(),
|
'review_form': forms.ReviewForm(),
|
||||||
'comment_form': forms.CommentForm(),
|
'comment_form': forms.CommentForm(),
|
||||||
|
'next': next_page if activity_count > (page_size * page) else None,
|
||||||
|
'prev': prev_page if page > 1 else None,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'feed.html', data)
|
return TemplateResponse(request, 'feed.html', data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_activity_feed(user, filter_level, model=models.Status):
|
||||||
|
''' get a filtered queryset of statuses '''
|
||||||
|
# status updates for your follow network
|
||||||
|
following = models.User.objects.filter(
|
||||||
|
Q(followers=user) | Q(id=user.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
activities = model
|
||||||
|
if hasattr(model, 'objects'):
|
||||||
|
activities = model.objects
|
||||||
|
|
||||||
|
activities = activities.order_by(
|
||||||
|
'-created_date'
|
||||||
|
)
|
||||||
|
if hasattr(activities, 'select_subclasses'):
|
||||||
|
activities = activities.select_subclasses()
|
||||||
|
|
||||||
|
# TODO: privacy relationshup between request.user and user
|
||||||
|
if filter_level in ['friends', 'home']:
|
||||||
|
# people you follow and direct mentions
|
||||||
|
activities = activities.filter(
|
||||||
|
Q(user__in=following, privacy='public') | \
|
||||||
|
Q(mention_users=user)
|
||||||
|
)
|
||||||
|
elif filter_level == 'self':
|
||||||
|
activities = activities.filter(user=user, privacy='public')
|
||||||
|
elif filter_level == 'local':
|
||||||
|
# everyone on this instance
|
||||||
|
activities = activities.filter(user__local=True, privacy='public')
|
||||||
|
else:
|
||||||
|
# all activities from everyone you federate with
|
||||||
|
activities = activities.filter(privacy='public')
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
|
||||||
def books_page(request):
|
def books_page(request):
|
||||||
''' discover books '''
|
''' discover books '''
|
||||||
recent_books = models.Work.objects
|
recent_books = models.Work.objects
|
||||||
|
@ -188,11 +215,7 @@ def user_page(request, username, subpage=None):
|
||||||
else:
|
else:
|
||||||
shelves = get_user_shelf_preview(user)
|
shelves = get_user_shelf_preview(user)
|
||||||
data['shelves'] = shelves
|
data['shelves'] = shelves
|
||||||
activities = models.Status.objects.filter(
|
activities = get_activity_feed(user, 'self')[:15]
|
||||||
user=user,
|
|
||||||
).order_by(
|
|
||||||
'-created_date',
|
|
||||||
).select_subclasses().all()[:10]
|
|
||||||
data['activities'] = activities
|
data['activities'] = activities
|
||||||
return TemplateResponse(request, 'user.html', data)
|
return TemplateResponse(request, 'user.html', data)
|
||||||
|
|
||||||
|
@ -339,23 +362,7 @@ def book_page(request, book_identifier, tab='friends'):
|
||||||
user=request.user,
|
user=request.user,
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
if tab == 'friends':
|
reviews = get_activity_feed(request.user, tab, model=book_reviews)
|
||||||
reviews = book_reviews.filter(
|
|
||||||
Q(user__followers=request.user, privacy='public') | \
|
|
||||||
Q(user=request.user) | \
|
|
||||||
Q(mention_users=request.user),
|
|
||||||
)
|
|
||||||
elif tab == 'local':
|
|
||||||
reviews = book_reviews.filter(
|
|
||||||
Q(privacy='public') | \
|
|
||||||
Q(mention_users=request.user),
|
|
||||||
user__local=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
reviews = book_reviews.filter(
|
|
||||||
Q(privacy='public') | \
|
|
||||||
Q(mention_users=request.user),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# TODO: books can be on multiple shelves
|
# TODO: books can be on multiple shelves
|
||||||
|
@ -408,6 +415,14 @@ def book_page(request, book_identifier, tab='friends'):
|
||||||
'active_tab': tab,
|
'active_tab': tab,
|
||||||
'path': '/book/%s' % book_identifier,
|
'path': '/book/%s' % book_identifier,
|
||||||
'cover_form': forms.CoverForm(instance=book),
|
'cover_form': forms.CoverForm(instance=book),
|
||||||
|
'info_fields': [
|
||||||
|
{'name': 'ISBN', 'value': book.isbn},
|
||||||
|
{'name': 'OCLC number', 'value': book.oclc_number},
|
||||||
|
{'name': 'OpenLibrary ID', 'value': book.openlibrary_key},
|
||||||
|
{'name': 'Goodreads ID', 'value': book.goodreads_key},
|
||||||
|
{'name': 'Format', 'value': book.physical_format},
|
||||||
|
{'name': 'Pages', 'value': book.pages},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'book.html', data)
|
return TemplateResponse(request, 'book.html', data)
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ def nodeinfo(request):
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"software": {
|
"software": {
|
||||||
"name": "mastodon",
|
"name": "fedireads",
|
||||||
"version": "0.0.1"
|
"version": "0.0.1"
|
||||||
},
|
},
|
||||||
"protocols": [
|
"protocols": [
|
||||||
|
|
Loading…
Reference in a new issue