Skip to content

Commit e6c2f33

Browse files
ragipidavidjimchamppre-commit-ci[bot]
authored
9509/feature/adding following functionality to lists in book pages (#10926)
Adds followable List cards to book pages --------- Co-authored-by: jimchamp <28732543+jimchamp@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e25dbb4 commit e6c2f33

File tree

9 files changed

+279
-33
lines changed

9 files changed

+279
-33
lines changed

openlibrary/i18n/messages.pot

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,8 @@ msgid "See more books by, and learn about, this author"
522522
msgstr ""
523523

524524
#: ProlificAuthors.html account/loans.html authors/index.html
525-
#: lists/showcase.html publishers/view.html search/authors.html
526-
#: search/publishers.html search/subjects.html
525+
#: lists/list_follow.html lists/showcase.html publishers/view.html
526+
#: search/authors.html search/publishers.html search/subjects.html
527527
#, python-format
528528
msgid "%(count)d book"
529529
msgid_plural "%(count)d books"
@@ -3053,8 +3053,7 @@ msgstr ""
30533053
msgid "My lists"
30543054
msgstr ""
30553055

3056-
#: account/sidebar.html type/edition/view.html type/user/view.html
3057-
#: type/work/view.html
3056+
#: account/sidebar.html type/user/view.html
30583057
msgid "See All"
30593058
msgstr ""
30603059

@@ -6422,10 +6421,14 @@ msgstr ""
64226421
msgid "No description."
64236422
msgstr ""
64246423

6425-
#: lists/home.html lists/showcase.html
6424+
#: lists/home.html lists/showcase.html lists/widget.html
64266425
msgid "See all"
64276426
msgstr ""
64286427

6428+
#: lists/list_follow.html lists/widget.html my_books/dropdown_content.html
6429+
msgid "You"
6430+
msgstr ""
6431+
64296432
#: lists/list_overview.html lists/widget.html my_books/dropdown_content.html
64306433
msgid "See this list"
64316434
msgstr ""
@@ -6519,10 +6522,6 @@ msgstr[1] ""
65196522
msgid "from"
65206523
msgstr ""
65216524

6522-
#: lists/widget.html my_books/dropdown_content.html
6523-
msgid "You"
6524-
msgstr ""
6525-
65266525
#: lists/widget.html
65276526
msgid "Loading<span class=\"loading-ellipsis\">...</span>"
65286527
msgstr ""

openlibrary/macros/Follow.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
$def with (publisher, fid='', following=False)
1+
$def with (publisher, fid='', following=False, request_path='')
22

33
$ subscriber = ctx.user and ctx.user.key
4-
5-
4+
$ custom_request_path = request_path if request_path != '' else request.fullpath
65
<div>
76
$if ctx.user:
87
<form class="follow-form" id="follow_$fid" method="POST" action="$(subscriber)/follows.json"
98
property="follow" typeof="Follows" vocab="http://schema.org/">
109
<input type="hidden" value="$following" name="state"/>
1110
<input type="hidden" value="$publisher" name="publisher"/>
12-
<input type="hidden" value="$request.fullpath" name="redir_url"/>
11+
<input type="hidden" value="$custom_request_path" name="redir_url"/>
1312
$ css_treatment = 'delete' if following else 'primary'
1413
<button type="submit" class="cta-btn cta-btn--$(css_treatment)">$_("Unfollow" if following else "Follow")</button>
1514
</form>

openlibrary/plugins/openlibrary/lists.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ def get_user_lists(seed_info):
238238
return [get_list_data(user_list, seed) for user_list in user_lists]
239239

240240

241+
@public
242+
def convert_list(list):
243+
site = web.ctx.site
244+
data = site._load(list)
245+
newList = List(site, list, data)
246+
return newList
247+
248+
241249
class lists_partials(delegate.page):
242250
path = "/lists/partials"
243251
encoding = "json"

openlibrary/plugins/upstream/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,12 @@ def handle_endtag(self, tag):
15521552
self.data.append('\n' if tag in ('p', 'li') else ' ')
15531553

15541554

1555+
@public
1556+
def get_user_object(username):
1557+
user = web.ctx.site.get(f'/people/{username}')
1558+
return user
1559+
1560+
15551561
@public
15561562
def reformat_html(html_str: str, max_length: int | None = None) -> str:
15571563
"""
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
$def with(lists, limit, user_key)
2+
3+
$def render_follow_button(owner_username, is_subscribed):
4+
$ request = "/people/" + owner_username
5+
$:macros.Follow(owner_username, following=is_subscribed, request_path=request)
6+
7+
$def list_card(list, owner, own_list):
8+
$ has_owner = owner
9+
$ cached_info = list.get_patron_showcase()
10+
$ count = cached_info["count"]
11+
<div class="list-follow-card">
12+
<a class="list-follow-card__header" href="$list.get_url()">
13+
<div class="list-follow-card__title">$cached_info["title"]</div>
14+
<div class="list-follow-card__num-books">
15+
$ungettext("%(count)d book", "%(count)d books", count, count=count)
16+
</div>
17+
</a>
18+
<a class="list-follow-card__covers" href="$list.get_url()">
19+
$for img_url in cached_info["covers"]:
20+
$if img_url:
21+
$ img_url = img_url.replace("-S.jpg", "-M.jpg")
22+
$else:
23+
$ img_url = '/images/icons/avatar_book-sm.png'
24+
<img src="$img_url" loading="lazy" width="80"/>
25+
</a>
26+
<div class="list-follow-card__bottom">
27+
<div class="list-follow-card__user">
28+
<a href="$owner.key">
29+
<img src="$(owner.key)/avatar" />
30+
</a>
31+
<div class="list-follow-card__username">
32+
<a class="list-follow-card__username-link" href="$owner.key">
33+
$if not own_list:
34+
$ owner_username = owner.key.split('/')[-1]
35+
$('@' + owner_username)
36+
$else:
37+
$_('You')
38+
</a>
39+
</div>
40+
</div>
41+
<div class="list-follow-card__follow-button">
42+
$if not own_list:
43+
$ owner_username = owner.key.split('/')[-1]
44+
$ owner_account = get_user_object(owner_username)
45+
$ is_subscribed = ctx.user and ctx.user.is_subscribed_user(owner_username)
46+
$ settings = owner_account.get_users_settings()
47+
$ is_public = settings and settings.get('public_readlog', 'no') == "yes"
48+
$if is_public:
49+
$:render_follow_button(owner_username, is_subscribed)
50+
</div>
51+
</div>
52+
</div>
53+
54+
$ count = 0
55+
$for i, list in enumerate(lists):
56+
$if count < limit:
57+
$ own_list = list.owner and list.owner.key == user_key
58+
$ converted = convert_list(list.key)
59+
$:list_card(converted, list.owner, own_list)
60+
$ count = count + 1
61+

openlibrary/templates/lists/widget.html

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,6 @@
2626
$if list['active']:
2727
$:render_template('lists/list_overview', list, user_key, seed_info)
2828

29-
$def render_widget_display(lists, limit, user_key, exclude_own_lists):
30-
$ count = 0
31-
$for i, list in enumerate(lists):
32-
$if count < limit:
33-
$if exclude_own_lists:
34-
$ own_list = list.owner and list.owner.key == user_key
35-
$if not own_list:
36-
$:render_template('lists/list_overview', list, user_key, seed_info)
37-
$ count = count + 1
38-
$else:
39-
$:render_template('lists/list_overview', list, user_key, seed_info)
40-
$ count = count + 1
41-
4229
$def render_head(page_lists, page_url):
4330
<div>
4431
<h3 class="header">
@@ -67,6 +54,18 @@ <h3 class="header">
6754
$ }
6855
<input type="hidden" name="list-i18n-strings" value="$json_encode(show_list_i18n_strings)">
6956

57+
$def render_showcase():
58+
<div class="carousel-section">
59+
<div class="list-follow-showcase">
60+
$:render_template('lists/list_follow', page_lists, 5, user_key)
61+
$if len(page_lists) > 5:
62+
<a
63+
class="list-follow-showcase__see-all cta-btn cta-btn--vanilla"
64+
href="$page.url()/lists"
65+
>$_("See all")</a>
66+
</div>
67+
</div>
68+
7069
$if include_widget:
7170
$if render_once('lists/widget.i18n-strings'):
7271
$:i18n_input()
@@ -78,14 +77,12 @@ <h3 class="header">
7877
$if async_load:
7978
<div class="list-overview-loading-indicator">$:_('Loading<span class="loading-ellipsis">...</span>')</div>
8079
$else:
81-
$:render_already_lists(user_lists, user_key)
80+
$:render_showcase()
8281
</ul>
8382
$if work and include_rating:
8483
$:macros.StarRatings(work, edition)
8584

8685
$if include_showcase:
87-
<ul id="list-lists" class="listLists">
88-
$:render_widget_display(page_lists, 3, user_key, exclude_own_lists)
89-
</ul>
86+
$:render_showcase()
9087

9188
</div>

openlibrary/templates/type/edition/view.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,6 @@ <h4>$:_('Links <span class="gray small sansserif">outside Open Library</span>')<
544544
<div class="workFooter tab-section lists-section" data-ids="$json_encode(data_ids)">
545545
<div class="lists-heading">
546546
<h2 class="lists-title">$_('Lists')</h2>
547-
<a class="hidden" href="$page.url()/lists">$_('See All')</a>
548547
</div>
549548
<div class="user-book-options">
550549
<div class="Tools tools-override">
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
@import (less) "../less/colors.less";
2+
@import (less) "../less/font-families.less";
3+
@import (less) "components/read-panel.less";
4+
@import (less) "components/book.less";
5+
6+
.list-follow-showcase {
7+
display: flex;
8+
overflow-x: auto;
9+
scrollbar-width: thin;
10+
padding: 8px 0;
11+
12+
.list-follow-card {
13+
margin-right: 24px;
14+
flex-shrink: 0;
15+
}
16+
17+
&__see-all {
18+
margin-right: 24px;
19+
align-self: center;
20+
width: auto;
21+
}
22+
}
23+
24+
// stylelint-disable-next-line no-descending-specificity
25+
.list-follow-card {
26+
a& {
27+
text-decoration: none;
28+
}
29+
30+
display: flex;
31+
flex-direction: column;
32+
33+
background-color: @beige;
34+
35+
@card-width: 215px;
36+
width: @card-width;
37+
height: 150px;
38+
39+
border: 1px solid fade(@black, 25%);
40+
border-radius: 4px;
41+
box-shadow: 2px 2px 4px fade(@black, 15%);
42+
43+
&__header {
44+
display: flex;
45+
flex-direction: column;
46+
justify-content: center;
47+
align-items: center;
48+
padding: 7px 10px;
49+
border-radius: 0 0 4px 4px;
50+
a& {
51+
text-decoration: none;
52+
}
53+
}
54+
&__name-avatar {
55+
display: flex;
56+
}
57+
&__bottom {
58+
background: @grey-f4f4f4;
59+
display: flex;
60+
justify-content: space-between;
61+
padding-bottom: 5px;
62+
padding-top: 5px;
63+
}
64+
&__user {
65+
display: flex;
66+
align-items: center;
67+
font-size: @font-size-label-medium;
68+
padding-left: 5px;
69+
70+
img {
71+
border-radius: 8px;
72+
width: 30px;
73+
height: 30px;
74+
margin-right: 5px;
75+
margin-top: -10px;
76+
position: relative;
77+
z-index: @z-index-level-3;
78+
border: 2px solid @white;
79+
}
80+
}
81+
82+
&__username {
83+
white-space: nowrap;
84+
overflow: hidden;
85+
text-overflow: ellipsis;
86+
text-decoration: none;
87+
padding-bottom: 5px;
88+
}
89+
&__username-link {
90+
a& {
91+
text-decoration: none;
92+
color: @dark-grey-two;
93+
padding-bottom: 5px;
94+
}
95+
}
96+
&__text {
97+
display: flex;
98+
flex-direction: column;
99+
}
100+
101+
&__follow-button {
102+
display: flex;
103+
justify-content: space-between;
104+
align-items: center;
105+
padding-right: 5px;
106+
padding-bottom: 3px;
107+
.cta-btn {
108+
font-size: 12px; // Override base size
109+
padding: 1px 10px; // Less padding
110+
border-radius: 4px; // Custom radius
111+
box-shadow: none; // Remove default shadow if present
112+
margin: 0;
113+
}
114+
}
115+
&__title {
116+
font-weight: bold;
117+
font-size: @font-size-label-large;
118+
color: @black;
119+
120+
white-space: nowrap;
121+
overflow: hidden;
122+
text-overflow: ellipsis;
123+
}
124+
125+
&__num-books {
126+
color: @grey-555;
127+
font-size: @font-size-label-medium;
128+
}
129+
&__info {
130+
flex: 1;
131+
flex-direction: row;
132+
}
133+
&__covers {
134+
@cover-width: 64px;
135+
@padding: 20px;
136+
137+
flex: 1;
138+
min-height: 1px; // fallback
139+
overflow: clip;
140+
141+
display: flex;
142+
align-items: center;
143+
padding: 0 @padding;
144+
145+
img {
146+
width: @cover-width;
147+
border-radius: 4px;
148+
box-shadow: 4px 4px 0 fade(@black, 25%);
149+
}
150+
151+
@top-cover: 3;
152+
@mid-cover: 2;
153+
@bottom-cover: 1;
154+
155+
@overlap: ((3 * @cover-width - (@card-width - 2 * @padding)) / 2);
156+
157+
img:nth-child(1) {
158+
z-index: @top-cover;
159+
transform: translate(0, @padding);
160+
}
161+
img:nth-child(2) {
162+
z-index: @mid-cover;
163+
transform: translate(-@overlap, @padding);
164+
}
165+
img:nth-child(3) {
166+
z-index: @bottom-cover;
167+
transform: translate(-2 * @overlap, @padding);
168+
}
169+
&:hover img {
170+
scale: 1.05;
171+
}
172+
}
173+
174+
// stylelint-disable-next-line no-descending-specificity
175+
img {
176+
transition: scale 0.2s;
177+
}
178+
}

0 commit comments

Comments
 (0)