mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-29 21:11:16 +00:00
User page
This commit is contained in:
parent
d28efe54dd
commit
c41b53bdbe
7 changed files with 180 additions and 108 deletions
|
@ -39,6 +39,7 @@ h2 {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#top-bar {
|
#top-bar {
|
||||||
|
@ -69,6 +70,9 @@ h2 {
|
||||||
#actions > * {
|
#actions > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
#actions > *:last-child {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
#notifications .icon {
|
#notifications .icon {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
@ -165,6 +169,21 @@ ul.menu a {
|
||||||
.row > * {
|
.row > * {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: min-content;
|
width: min-content;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
.row > *:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.row.shrink > * {
|
||||||
|
flex-grow: 0;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow-requests .row {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.follow-requests .row > *:first-child {
|
||||||
|
width: 15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login form {
|
.login form {
|
||||||
|
@ -196,9 +215,8 @@ button.secondary {
|
||||||
border: 2px solid #247BA0;
|
border: 2px solid #247BA0;
|
||||||
color: #247BA0;
|
color: #247BA0;
|
||||||
}
|
}
|
||||||
|
button.warning {
|
||||||
.login h2 {
|
background-color: #FF1654;
|
||||||
border-bottom: 3px solid #B2DBBF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
|
@ -235,6 +253,25 @@ button.secondary {
|
||||||
position: relative;
|
position: relative;
|
||||||
bottom: 0.35em;
|
bottom: 0.35em;
|
||||||
}
|
}
|
||||||
|
.user-pic.large {
|
||||||
|
width: 5em;
|
||||||
|
height: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile h2 a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9em;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.user-profile h2 .icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.user-profile .row > * {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
.user-profile .row > *:last-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.review-form label {
|
.review-form label {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -285,8 +322,19 @@ button.secondary {
|
||||||
.all-shelves > div:first-child > * {
|
.all-shelves > div:first-child > * {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
.all-shelves h2 {
|
|
||||||
border-bottom: 3px solid #B2DBBF;
|
.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 {
|
||||||
|
@ -378,7 +426,14 @@ button.secondary {
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
white-space: pre-wrap;
|
white-space: pre-line;
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
blockquote .icon-quote-open {
|
||||||
|
float: left;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.interaction {
|
.interaction {
|
||||||
|
@ -456,6 +511,7 @@ th, td {
|
||||||
.post h2, .compose-suggestion h2 {
|
.post h2, .compose-suggestion h2 {
|
||||||
position: relative;
|
position: relative;
|
||||||
right: 2em;
|
right: 2em;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
.post .time-ago {
|
.post .time-ago {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -487,6 +543,11 @@ th, td {
|
||||||
background-color: #DDD;
|
background-color: #DDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a .icon {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden-text {
|
.hidden-text {
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
|
@ -3,25 +3,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="all-shelves content-container">
|
<div class="all-shelves content-container">
|
||||||
{% for shelf in shelves %}
|
{% include 'snippets/covers_shelf.html' with shelves=shelves user=request.user %}
|
||||||
{% if shelf.books %}
|
|
||||||
<div>
|
|
||||||
<h2>{{ shelf.name }}
|
|
||||||
{% if shelf.size > shelf.books|length %}
|
|
||||||
<small>(<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>)</small>
|
|
||||||
{% endif %}
|
|
||||||
</h2>
|
|
||||||
<div class="covers-shelf {{ shelf.identifier }}">
|
|
||||||
{% for book in shelf.books %}
|
|
||||||
<div class="book-preview" onclick="show_compose(this)" id="book-{{ book.id }}">
|
|
||||||
{% include 'snippets/book_cover.html' with book=book %}
|
|
||||||
{% include 'snippets/shelve_button.html' with book=book %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for shelf in shelves %}
|
{% for shelf in shelves %}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
<img class="user-pic" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}">
|
<img class="user-pic{% if large %} large{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}">
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
{% load fr_display %}
|
{% load fr_display %}
|
||||||
{% for book in books %}
|
{% for shelf in shelves %}
|
||||||
<div class="book-preview">
|
{% if shelf.books %}
|
||||||
{% include 'snippets/book.html' with rating=rating %}
|
<div>
|
||||||
|
<h2>{{ shelf.name }}
|
||||||
|
{% if shelf.size > shelf.books|length %}
|
||||||
|
<small>(<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>)</small>
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
<div class="covers-shelf {{ shelf.identifier }}">
|
||||||
|
{% for book in shelf.books %}
|
||||||
|
<div class="book-preview" onclick="show_compose(this)" id="book-{{ book.id }}">
|
||||||
|
{% include 'snippets/book_cover.html' with book=book %}
|
||||||
|
{% include 'snippets/shelve_button.html' with book=book %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<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 }}">
|
||||||
<input type=submit value="Accept">
|
<button 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 }}">
|
||||||
<input type=submit value="Delete">
|
<button type="submit" class="warning">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,64 +1,61 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
{% load humanize %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="sidebar">
|
<div class="content-container user-profile">
|
||||||
<div class="user-profile">
|
<h2>User Profile
|
||||||
<h2>
|
{% if is_self %}
|
||||||
{% include 'snippets/avatar.html' with user=user %}
|
<a href="/user-edit/">edit
|
||||||
{% if user.name %}{{ user.name }}{% endif %}
|
<span class="icon icon-pencil">
|
||||||
<small>{{ user.username }}</small>
|
<span class="hidden-text">Edit profile</span>
|
||||||
</h2>
|
</span>
|
||||||
{% if user.summary %}
|
</a>
|
||||||
<blockquote>{{ user.summary | safe }}</blockquote>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="pic-container">
|
||||||
|
{% include 'snippets/avatar.html' with user=user large=True %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}</p>
|
||||||
|
<p>{{ user.username }}</p>
|
||||||
|
<p>Joined {{ user.created_date | naturaltime }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user.summary %}
|
||||||
|
<blockquote><span class="icon icon-quote-open"></span>{{ user.summary | safe }}</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% if not is_self %}
|
{% if not is_self %}
|
||||||
{% include 'snippets/follow_button.html' with user=user %}
|
{% include 'snippets/follow_button.html' with user=user %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_self %}
|
|
||||||
<div class="interaction">
|
|
||||||
<a href="/user-edit/">Edit profile</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if is_self and user.follower_requests.all %}
|
{% if is_self and user.follower_requests.all %}
|
||||||
<div>
|
<div class="follow-requests">
|
||||||
<h2>Follow Requests</h2>
|
<h2>Follow Requests</h2>
|
||||||
{% for requester in user.follower_requests.all %}
|
{% for requester in user.follower_requests.all %}
|
||||||
<div>
|
<div class="row shrink">
|
||||||
|
<p>
|
||||||
{% include 'snippets/username.html' with user=requester show_full=True %}
|
{% include 'snippets/username.html' with user=requester show_full=True %}
|
||||||
|
</p>
|
||||||
{% include 'snippets/follow_request_buttons.html' with user=requester %}
|
{% include 'snippets/follow_request_buttons.html' with user=requester %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
|
||||||
<h2>Followers</h2>
|
|
||||||
{% for follower in user.followers.all %}
|
|
||||||
<div>
|
|
||||||
{% include 'snippets/username.html' with user=follower show_full=True %}
|
|
||||||
{% include 'snippets/follow_button.html' with user=follower %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Following</h2>
|
|
||||||
{% for following in user.following.all %}
|
|
||||||
<div>
|
|
||||||
{% include 'snippets/username.html' with user=following show_full=True %}
|
|
||||||
{% include 'snippets/follow_button.html' with user=following %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content">
|
<div class="all-shelves content-container">
|
||||||
{% for shelf in shelves %}
|
{% include 'snippets/covers_shelf.html' with shelves=shelves user=user %}
|
||||||
<div>
|
</div>
|
||||||
<h2>{{ shelf.name }}</h2>
|
|
||||||
{% include 'snippets/shelf.html' with shelf=shelf ratings=ratings %}
|
<div>
|
||||||
|
<div class="content-container"><h2>User Activity</h2></div>
|
||||||
|
{% for activity in activities %}
|
||||||
|
<div class="content-container">
|
||||||
|
{% include 'snippets/status.html' with status=activity %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,39 +37,20 @@ def home(request):
|
||||||
def home_tab(request, tab):
|
def home_tab(request, tab):
|
||||||
''' user's homepage with activity feed '''
|
''' user's homepage with activity feed '''
|
||||||
shelves = []
|
shelves = []
|
||||||
book_count = 6
|
shelves = get_user_shelf_preview(
|
||||||
for (identifier, count) in [('reading', 3), ('read', 1), ('to-read', 3)]:
|
request.user,
|
||||||
if book_count <= 0:
|
[('reading', 3), ('read', 1), ('to-read', 3)]
|
||||||
break
|
|
||||||
shelf = models.Shelf.objects.get(
|
|
||||||
user=request.user,
|
|
||||||
identifier=identifier,
|
|
||||||
)
|
)
|
||||||
if not shelf.books.count():
|
size = sum(len(s['books']) for s in shelves)
|
||||||
continue
|
|
||||||
books = models.ShelfBook.objects.filter(
|
|
||||||
shelf=shelf,
|
|
||||||
).order_by(
|
|
||||||
'-updated_date'
|
|
||||||
)[:count]
|
|
||||||
|
|
||||||
book_count -= len(books)
|
|
||||||
|
|
||||||
shelves.append({
|
|
||||||
'name': shelf.name,
|
|
||||||
'identifier': shelf.identifier,
|
|
||||||
'books': [b.book for b in books],
|
|
||||||
'size': shelf.books.count(),
|
|
||||||
})
|
|
||||||
# books new to the instance, for discovery
|
# books new to the instance, for discovery
|
||||||
if book_count > 0:
|
if size < 6:
|
||||||
shelves.append({
|
shelves.append({
|
||||||
'name': 'Recently added',
|
'name': 'Recently added',
|
||||||
'identifier': None,
|
'identifier': None,
|
||||||
'books': models.Book.objects.order_by(
|
'books': models.Book.objects.order_by(
|
||||||
'-created_date'
|
'-created_date'
|
||||||
)[:book_count],
|
)[:6 - size],
|
||||||
'count': book_count,
|
'count': 6 - size,
|
||||||
})
|
})
|
||||||
|
|
||||||
# allows us to check if a user has shelved a book
|
# allows us to check if a user has shelved a book
|
||||||
|
@ -138,7 +119,6 @@ def notifications_page(request):
|
||||||
notifications.update(read=True)
|
notifications.update(read=True)
|
||||||
return TemplateResponse(request, 'notifications.html', data)
|
return TemplateResponse(request, 'notifications.html', data)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def user_page(request, username):
|
def user_page(request, username):
|
||||||
''' profile page for a user '''
|
''' profile page for a user '''
|
||||||
|
@ -153,15 +133,19 @@ def user_page(request, username):
|
||||||
# otherwise we're at a UI view
|
# otherwise we're at a UI view
|
||||||
|
|
||||||
# TODO: change display with privacy and authentication considerations
|
# TODO: change display with privacy and authentication considerations
|
||||||
shelves = models.Shelf.objects.filter(user=user)
|
shelves = get_user_shelf_preview(user)
|
||||||
ratings = {r.book.id: r.rating for r in \
|
|
||||||
models.Review.objects.filter(user=user, book__shelves__user=user)}
|
activities = models.Status.objects.filter(
|
||||||
|
user=user,
|
||||||
|
).order_by(
|
||||||
|
'-created_date',
|
||||||
|
).select_subclasses()[:10]
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'user': user,
|
'user': user,
|
||||||
'shelves': shelves,
|
'shelves': shelves,
|
||||||
'ratings': ratings,
|
|
||||||
'is_self': request.user.id == user.id,
|
'is_self': request.user.id == user.id,
|
||||||
|
'activities': activities,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'user.html', data)
|
return TemplateResponse(request, 'user.html', data)
|
||||||
|
|
||||||
|
@ -390,3 +374,37 @@ def shelf_page(request, username, shelf_identifier):
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'shelf.html', data)
|
return TemplateResponse(request, 'shelf.html', data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_shelf_preview(user, shelf_proportions=None):
|
||||||
|
''' data for the covers shelf (user page and feed page) '''
|
||||||
|
shelves = []
|
||||||
|
shelf_max = 6
|
||||||
|
if not shelf_proportions:
|
||||||
|
shelf_proportions = [('reading', 3), ('read', 2), ('to-read', -1)]
|
||||||
|
for (identifier, count) in shelf_proportions:
|
||||||
|
if shelf_max <= 0:
|
||||||
|
break
|
||||||
|
if count > shelf_max or count < 0:
|
||||||
|
count = shelf_max
|
||||||
|
shelf = models.Shelf.objects.get(
|
||||||
|
user=user,
|
||||||
|
identifier=identifier,
|
||||||
|
)
|
||||||
|
if not shelf.books.count():
|
||||||
|
continue
|
||||||
|
books = models.ShelfBook.objects.filter(
|
||||||
|
shelf=shelf,
|
||||||
|
).order_by(
|
||||||
|
'-updated_date'
|
||||||
|
)[:count]
|
||||||
|
|
||||||
|
shelf_max -= len(books)
|
||||||
|
|
||||||
|
shelves.append({
|
||||||
|
'name': shelf.name,
|
||||||
|
'identifier': shelf.identifier,
|
||||||
|
'books': [b.book for b in books],
|
||||||
|
'size': shelf.books.count(),
|
||||||
|
})
|
||||||
|
return shelves
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue