Skip to content

Commit 647574e

Browse files
committed
Add book tagging mechanism
1 parent ba6667c commit 647574e

File tree

8 files changed

+212
-3
lines changed

8 files changed

+212
-3
lines changed

Rules

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ preprocess do
3535
item.identifier =~ '/cover/*' && ignored_isbns.include?(item.identifier.without_exts.delete_prefix('/cover/'))
3636
end
3737

38+
generate_tag_items
39+
3840
missing_covers = 0
3941

4042
@items.select { |item| item.identifier =~ '/index.*' || book?(item) }.each do |item|
@@ -150,7 +152,6 @@ end
150152
%i[mini medium default].each do |repr|
151153
compile '/cover/**/*', rep: repr do
152154
isbn = item.identifier.without_exts.split('/')[-1]
153-
next if repr == :medium && !@config[:last_readings_isbn].include?(isbn)
154155

155156
width = @config[:cover][:width][repr]
156157
filter :thumbnailize, width: width unless @config[:dev]
@@ -174,7 +175,6 @@ compile '/book/**/*', rep: :feed do
174175
end
175176

176177
compile '/book/**/*', rep: :card do
177-
next unless @config[:last_readings_isbn].include?(item[:isbn])
178178
filter :erb
179179
# no need to apply kramdown, book_card only uses book metadata, not content
180180
filter :html_compressor, type: item[:extension] unless @config[:dev]
@@ -197,6 +197,17 @@ compile '/book/**/*' do
197197
write item.identifier.without_ext + '/index.html'
198198
end
199199

200+
compile '/tag/*' do
201+
filter :erb
202+
filter :kramdown
203+
filter :html_compressor, type: item[:extension] unless @config[:dev]
204+
205+
layout '/tag.*'
206+
layout '/default.*'
207+
208+
write "#{item.identifier.without_exts}.html"
209+
end
210+
200211
compile '/api/v1/*' do
201212
filter :erb
202213
layout item[:layout] unless item[:layout] == 'none'
@@ -257,6 +268,10 @@ route '/last-readings.*' do
257268
"/last-readings.html"
258269
end
259270

271+
route '/all-tags.*' do
272+
"/tag/index.html"
273+
end
274+
260275
route '/index-noscript.*' do
261276
"/index-noscript.html"
262277
end

content/all-tags.html.erb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: Les étiquettes
3+
layout: default
4+
---
5+
6+
<h2>Toutes les étiquettes</h2>
7+
<%
8+
tags = tag_cloud
9+
if tags.empty?
10+
%>
11+
<p>Aucune étiquette n'a été associée à aucun livre.</p>
12+
<% else %>
13+
<ul class="tag-cloud">
14+
<% tags.each do |tag, tag_weight, books_count| %>
15+
<li>
16+
<span class="tag_<%= tag_weight %>">
17+
<a href="<%= @items[tag_item_identifier(tag)].path %>"><span class="tag"><%= tag %></span></a>&nbsp;<span class="tag-weight">(<%= books_count %>)</span>
18+
</span>
19+
</li>
20+
<% end %>
21+
</ul>
22+
<% end %>

content/static/style.css

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,78 @@ blockquote::before {
541541
#only-favorites:target .temporal-section:not(:has(.book-favorite)) {
542542
display: none;
543543
}
544+
545+
.tag-cloud {
546+
margin: 0 auto;
547+
padding: 0;
548+
text-align: center;
549+
}
550+
551+
.tag-cloud li::before {
552+
content: none;
553+
}
554+
555+
.tag-cloud li {
556+
display: inline;
557+
margin: 0;
558+
padding: 1em;
559+
line-height: 3em;
560+
}
561+
562+
.tag-weight {
563+
font-size: .6em;
564+
color: rgba(0, 0, 0, .7);
565+
}
566+
567+
@media (prefers-color-scheme: dark) {
568+
.tag-weight {
569+
color: rgba(255, 255, 255, .7);
570+
}
571+
}
572+
573+
.tag-cloud a {
574+
text-decoration: none;
575+
}
576+
577+
.tag {
578+
white-space: nowrap;
579+
}
580+
581+
.tag_0 { font-size: .7em; font-weight: 100; }
582+
.tag_1 { font-size: .9em; font-weight: 100; }
583+
.tag_2 { font-size: 1.1em; font-weight: 200; }
584+
.tag_3 { font-size: 1.3em; font-weight: 300; }
585+
.tag_4 { font-size: 1.5em; font-weight: 400; }
586+
.tag_5 { font-size: 1.7em; font-weight: 500; }
587+
.tag_6 { font-size: 1.9em; font-weight: 600; }
588+
.tag_7 { font-size: 2.1em; font-weight: 700; }
589+
.tag_8 { font-size: 2.4em; font-weight: 800; }
590+
.tag_9 { font-size: 2.6em; font-weight: 900; }
591+
.tag_10 { font-size: 2.6em; font-weight: 900; }
592+
593+
.book-tags {
594+
margin: 0;
595+
}
596+
597+
.book-tags li {
598+
display: inline-block;
599+
margin: 0 2px;
600+
}
601+
602+
.book-tags li a {
603+
border-radius: 4px;
604+
border: 1px solid rgba(0, 0, 0, .3);
605+
padding: 2px 4px;
606+
font-family: sans-serif;
607+
font-size: 0.7em;
608+
text-transform: lowercase;
609+
background-color: rgba(0, 0, 0, .05);
610+
transition: background-color .6s ease;
611+
}
612+
.book-tags li a:hover {
613+
background-color: rgba(0, 0, 0, .15);
614+
}
615+
616+
.book-tags li::before {
617+
content: none;
618+
}

fetch_book.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ title: \"${title}\"
175175
author: \"${author}\"
176176
rating: ${rating}
177177
read_date: $(date "+%Y-%m-%d") # TBD
178+
tags:
179+
- TBD
178180
---
179181
180182
${description}

layouts/book.html.erb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,16 @@ unless @items["/cover/#{@item[:isbn]}.*"].nil?
2525

2626
<%= yield %>
2727

28-
<div style="clear: left;"></div>
28+
<div style="clear: left; margin-bottom: 1em;"></div>
29+
30+
<% unless @item[:tags].nil? || @item[:tags].empty? %>
31+
<ul class="book-tags">
32+
<li><span title="Étiquettes">🏷️</span></li>
33+
<% @item[:tags].each do |tag| %>
34+
<li><%= link_to_tag(tag) %></li>
35+
<% end %>
36+
</ul>
37+
<% end %>
2938

3039
<%
3140
linked_books = @item.fetch(:linked_books, []).map do |isbn|

layouts/head_book.html.erb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
<meta property="book:author" content="<%= @item[:author] %>" />
33
<meta property="book:isbn" content="<%= format_isbn(@item[:isbn]) %>" />
44
<%# <meta property="book:release_date" content="TODO" /> %>
5+
<% unless @item[:tags].nil? %>
6+
<% @item[:tags].each do |tag| %>
7+
<meta property="book:tag" content="<%= tag %>">
8+
<% end %>
9+
<% end %>
510
<%
611
picture = @items["/cover/#{@item[:isbn]}.*"]
712
unless picture.nil?

layouts/tag.html.erb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<h2>Les livres étiquettés "<%= @item[:title] %>" <span class="title-more"><%= link_to('🏷️ Toutes les étiquettes', @items['/all-tags.*']) %></a></h2>
2+
<% if @item[:books].nil? || @item[:books].empty? %>
3+
<%= yield %>
4+
<% else %>
5+
<div class="book-grid">
6+
<%
7+
@item[:books].each do |book_identifier|
8+
book = @items[book_identifier]
9+
%>
10+
<%= book.reps[:card].compiled_content(snapshot: :card) %>
11+
<% end %>
12+
</div>
13+
<% end %>

lib/helpers.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'cgi'
77

88
use_helper Nanoc::Helpers::Rendering
9+
use_helper Nanoc::Helpers::LinkTo
910

1011
def h(text)
1112
CGI.escapeHTML(text.nil? ? '' : text)
@@ -28,6 +29,7 @@ def to_json(book, url: :relative)
2829
'favorite': book[:favorite] || false,
2930
'cover': "#{url_prefix}#{cover_path}" || '',
3031
'cover_mini': "#{url_prefix}#{cover_mini_path}" || '',
32+
'tags': book[:tags] || []
3133
}
3234
end
3335

@@ -151,4 +153,70 @@ def recent_books()
151153

152154
def feed_books()
153155
last_readings(@config[:site][:feed][:max_entries])
156+
end
157+
158+
def all_tags
159+
tags = {}
160+
@items.each do |item|
161+
next if item[:tags].nil? || item[:tags].empty? || !item[:tag].nil?
162+
163+
item[:tags].each do |tag|
164+
tags[tag] = [] if tags[tag].nil?
165+
tags[tag] << item unless tags[tag].include? item
166+
end
167+
end
168+
# tags.each do |tag, _|
169+
# tags[tag] = tags[tag].sort_by { |item| Date.parse(item[:datetime].to_s) }.reverse
170+
# end
171+
tags
172+
end
173+
174+
def tag_cloud
175+
# sort on hash sort by key which is what we want
176+
tags = all_tags.sort
177+
result = []
178+
return result if tags.empty?
179+
# tag_<weight> goes from tag_0 to tag_10, map tag weight value to [0,10]
180+
max = tags.max_by { |_, v| v.size }[1].size
181+
min = tags.min_by { |_, v| v.size }[1].size
182+
tags.each do |tag, posts|
183+
posts_count = posts.size
184+
tag_weight = (((posts_count - min) * 10.0) / max).round
185+
result << [tag, tag_weight, posts_count]
186+
end
187+
result
188+
end
189+
190+
def tag_slug(tag)
191+
I18n.transliterate(tag.downcase).gsub(/\W/, '-').gsub(/-+/, '-').strip
192+
end
193+
194+
def tag_item_identifier(tag)
195+
"/tag/#{tag_slug(tag)}.*"
196+
end
197+
198+
def link_to_tag(tag)
199+
tag_item = @items[tag_item_identifier(tag)]
200+
link_to(tag_item[:title], tag_item)
201+
end
202+
203+
def generate_tag_items
204+
all_tags.each do |tag, books|
205+
# item's content is used as fallback when no post can be found.
206+
@items.create("
207+
Aucun livre n'est étiquetté avec \"<%= @item[:title] %>\".
208+
209+
Voir la liste des <%= link_to('autres étiquettes', @items['/all-tags.*']) %>.
210+
", {
211+
title: tag,
212+
tag: tag_slug(tag),
213+
layout: 'tags',
214+
# can't store item ref, from the preprocess block, the item view isn't suitable for future use
215+
# such as compiled_content, path, reps and so on.
216+
# only store identifier and request item from identifier at call site
217+
books: natural_sort(books).map { |p| p.identifier.to_s },
218+
extension: 'md'
219+
}, Nanoc::Identifier.new(tag_item_identifier(tag))
220+
)
221+
end
154222
end

0 commit comments

Comments
 (0)