Skip to content

Commit cd07bc6

Browse files
committed
People: introduce verbosed optech mentions
show paragraph snippet for each mention in the people-index
1 parent bda0dcd commit cd07bc6

File tree

6 files changed

+149
-26
lines changed

6 files changed

+149
-26
lines changed

_includes/optech-mention.html

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div class="backlink-box">
2+
<div class="backlink-header">
3+
#{{ reference.newsletter_number }} > {{ reference.header }}{% unless reference.title == nil %} > {{ reference.title }}{% endunless %}
4+
</div>
5+
{{ reference.paragraph | link_to_anchor: reference.url | markdownify }}
6+
</div>

_layouts/person.html

+3-14
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,6 @@
3030
{% capture primary_sources %}{{primary_sources}}{{newline}}- {{reference}}{% endcapture %}
3131
{% endfor %}
3232

33-
<!-- Build list of internal optech mentions -->
34-
{% assign references = '' %}
35-
{% for mention in page.optech_mentions %}
36-
{% if mention.feature == true %}
37-
{% assign bold = '{:.bold}' %}
38-
{% else %}
39-
{% assign bold='' %}
40-
{% endif %}
41-
{% include functions/get-mention-date.md %}
42-
{% capture references %}{{references}}{{date}}- [{{mention.title}}]({{mention.url}}){{bold}}ENDENTRY{% endcapture %}
43-
{% endfor %}
4433

4534
<!-- Build list of see also entries -->
4635
{% for source in page.see_also %}
@@ -71,13 +60,13 @@
7160
{%- if page.optech_mentions and page.optech_mentions != '' -%}
7261
## Optech newsletter mentions
7362

74-
{% assign sorted_references = references | split: 'ENDENTRY' | sort | reverse %}
63+
{% assign sorted_references = page.optech_mentions | sort_by_newsletter_number | reverse %}
7564
{%- for reference in sorted_references -%}
76-
{%- assign current_ref_year = reference | slice: 0, 4 -%}
65+
{%- assign current_ref_year = reference.year -%}
7766
{%- if current_ref_year != last_ref_year -%}
7867
{{newline}}{{newline}}**{{current_ref_year}}**
7968
{%- endif -%}
80-
{{newline}}{{reference | slice: 10, 9999999999 }}
69+
{{newline}}{% include optech-mention.html %}
8170
{%- assign last_ref_year = current_ref_year -%}
8271
{%- endfor -%}
8372
{% endif %}{{newline}}{{newline}}

_plugins/auto-anchor.rb

+22-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
# - [Summary][]: Details
88
# - [Summary](URL): Details
99

10+
def generate_anchor_list_link(anchor_link)
11+
# custom clickable bullet linking to an anchor
12+
"<a href=\"#{anchor_link}\" class=\"anchor-list-link\">●</a>"
13+
end
14+
1015
def auto_anchor(content)
1116
content.gsub!(/^ *- .*/) do |string|
1217
## Find shortest match for **bold**, *italics*, or [markdown][links]
@@ -17,7 +22,7 @@ def auto_anchor(content)
1722
string
1823
else
1924
slug = generate_slug(title)
20-
id_prefix = "- {:#{slug} .anchor-list} <a href=\"#{slug}\" class=\"anchor-list-link\">●</a>"
25+
id_prefix = "- {:#{slug} .anchor-list} #{generate_anchor_list_link(slug)}"
2126
string.sub!(/-/, id_prefix)
2227
end
2328
end
@@ -49,3 +54,19 @@ def render(context)
4954

5055
Liquid::Template.register_tag('auto_anchor', Jekyll::RenderAutoAnchor)
5156

57+
module TextFilter
58+
# This is a custom filter used in `optech-mentions.html`
59+
# to add anchor links to each backlink snippet
60+
def link_to_anchor(text, url)
61+
id_prefix = generate_anchor_list_link(url)
62+
if text.start_with?("-")
63+
# snippet is already a list item
64+
text.sub!(/-/, id_prefix)
65+
else
66+
text.prepend("#{id_prefix} ")
67+
end
68+
text
69+
end
70+
end
71+
72+
Liquid::Template.register_filter(TextFilter)

_plugins/bidirectional_links_generator.rb

+83-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
# by allowing for automatic mentions using the double-bracket link syntax.
99
class BidirectionalLinksGenerator < Jekyll::Generator
1010
def generate(site)
11-
1211
# This is only supported for english
1312
lang = "en"
1413
all_pages = site.documents.select { |doc| doc.url.start_with?("/#{lang}/") }
@@ -82,6 +81,12 @@ def generate(site)
8281
HTML
8382
)
8483
end
84+
85+
# we need the topic links for manual substitutions of links later on
86+
@topics_links = site.collections["topics"].map do |topic|
87+
["topic #{topic.data["shortname"] || topic.data["title"]}", topic.url]
88+
end
89+
8590
# Newsletter mentions
8691
# =====================
8792
newsletter_pages = pages_with_link_syntax.select { |doc| doc.url.start_with?("/#{lang}/newsletters/") }
@@ -95,21 +100,43 @@ def generate(site)
95100
if page_in_question.content.include?(target_page_href)
96101
# The page_in_question mentions the current page, we now need to
97102
# find the specific mentions.
98-
mentions = get_mentions_of(page_in_question, target_page_href)
103+
mentions = get_mentions_of(page_in_question, target_page_href, current_page.collection.label)
99104
current_page.data["optech_mentions"] ||= [] # Initialize if not already present
100105
# Add the calculated mentions to `optech_mentions`
101106
# Note: a page might mentioning another page more than once
102107
mentions.each do |mention|
103-
current_page.data["optech_mentions"] << {
104-
"title" => mention["title"],
105-
"url" => mention["url"]
106-
}
108+
current_page.data["optech_mentions"] << mention
107109
end
108110
end
109111
end
110112
end
111113
end
112114

115+
def liquify(content, date)
116+
context = Liquid::Context.new({}, {}, { site: Jekyll.sites.first })
117+
context['page'] = { 'date' => date } # needed to identify deprecated_links
118+
template = Liquid::Template.parse(content)
119+
content_parsed = template.render(context)
120+
end
121+
122+
def get_external_links(page)
123+
# this assumes that a "{% include {references, linkers/issues}.md %}" line
124+
# exists at the end of the documents and external links are declared after it
125+
126+
# get all the references after the {% include _ %} line
127+
regex_for_first_include = /\{% include (?:references\.md|linkers\/issues\.md).*?%\}/
128+
references = page.content.split(regex_for_first_include, 2).last.strip
129+
references.prepend("{% include references.md %}\n")
130+
131+
# manually trigger the replacement of the {% include %} tags in order to
132+
# have all the required links ([key]:url) needed for the matching snippets
133+
references_parsed = liquify(references, page.date)
134+
135+
# Search for all occurrences of the pattern "[key]: url"
136+
# and return them in an array
137+
references_parsed.scan(/\[([^\]]+?)\]\s*:\s*(\S+)/i)
138+
end
139+
113140
def find_title(string)
114141
title = capture_group = ""
115142
## Find shortest match for **bold**, *italics*, or [markdown][links]
@@ -157,10 +184,10 @@ def extract_slug_from_manual_anchor(text)
157184
# - remove liquid anchor syntax from the result
158185
# - extract slug to use it on the generated anchor list link
159186
# example of this pattern can be seen in `en/newsletter/2019-06-12-newsletter.md`
160-
match = text.match(/\{:#(\w+)\}/)
187+
match = text.match(/\{:#([\w-]+)\}/)
161188
if match
162189
slug = "##{match[1]}" # extract slug
163-
text.sub!(/\{:#\w+\}\n?/, "") # Remove the {:#slug} syntax and optional trailing newline
190+
text.sub!(/#{match[0]}/, "") # Remove the matched {:#slug} syntax
164191
slug
165192
else
166193
nil
@@ -169,21 +196,30 @@ def extract_slug_from_manual_anchor(text)
169196

170197
# This method searches the content for paragraphs that link to the
171198
# the target page and returns these mentions
172-
def get_mentions_of(page, target_page_url)
199+
def get_mentions_of(page, target_page_url, collection)
173200
# This is called only when we know that a match exists
174201
# The logic here assumes that:
202+
# - paragraphs have headers
175203
# - each block of text (paragraph) is seperated by an empty line
176204
# - primary titles are enclosed in **bold**
177205
# - secondary (nested) titles are enclosed in *italics*
178206

179207
content = page.content
208+
external_links = collection == "people" ?
209+
get_external_links(page).reverse + @topics_links : [] # people-index specific
210+
180211
# Split the content into paragraphs
181212
paragraphs = content.split(/\n\n+/)
213+
# Find all the headers in the content
214+
headers = content.scan(/^#+\s+(.*)$/).flatten
182215

183216
# Create an array of hashes containing:
217+
# - the paragraph text
218+
# - the associated header
184219
# - the associated url
185-
# - the associated title
220+
# - the associated title (when is not part of the paragraph)
186221
matching_paragraphs = []
222+
current_header = 0
187223
current_title = []
188224

189225
# Iterate over all paragraphs to find those that match the given url
@@ -215,6 +251,19 @@ def get_mentions_of(page, target_page_url)
215251

216252
# If the current paragraph contains the URL, add it to the matching paragraphs
217253
if p.include?(target_page_url)
254+
if collection == "people"
255+
# Loop through the array of [key]:url_replace matches and replace
256+
# - the occurrences of "[key][]" with "[key](url_replace)"
257+
# - the occurrences of "[something][key]" with "[something](url_replace)"
258+
external_links.each do |match|
259+
key_pattern = match[0].gsub(/\s/, '\s+') # to work with multiline keys
260+
p.gsub!(/\[(#{key_pattern})\]\[\]/im, "[\\1](#{match[1]})")
261+
p.gsub!(/\[(.+?)\]\[(#{key_pattern})\]/im, "[\\1](#{match[1]})")
262+
end
263+
# manually replace common liquid variables in paragraph
264+
p.gsub!(/#{Regexp.escape("{{bse}}")}/,"https://bitcoin.stackexchange.com/a/")
265+
end
266+
218267
# generate slug for matching paragraph
219268
slug = extract_slug_from_manual_anchor(p)
220269
if slug.nil?
@@ -230,9 +279,33 @@ def get_mentions_of(page, target_page_url)
230279
"title"=> current_title.join(": "),
231280
"url" => "#{page.url}#{slug}"
232281
}
282+
if collection == "people"
283+
# People index has verbosed mentions
284+
matching_paragraph.merge!({
285+
"paragraph"=> p.lstrip,
286+
"header"=> headers[current_header],
287+
"newsletter_number" => page.title.sub("Bitcoin Optech Newsletter #", "").to_i,
288+
"year" => File.basename(page.path)[0, 4]
289+
})
290+
291+
if !title.empty?
292+
# paragraph has title
293+
# for the verbosed mentions we display the paragraph that contains
294+
# the mention (see `optech-mentions.html`), therefore we do not
295+
# need to repeat the title
296+
current_title.pop # this way we keep the parent title
297+
matching_paragraph["title"] = current_title[0]
298+
end
299+
end
233300
matching_paragraphs << matching_paragraph
234301
end
302+
303+
# update to the next header when parse through it
304+
if p.sub(/^#+\s*/, "") == headers[(current_header + 1) % headers.length()]
305+
current_header += 1
306+
end
235307
end
308+
236309
# Return the matching paragraphs
237310
matching_paragraphs
238311
end

_plugins/common_utils.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,15 @@ def generate_slug(title)
1414
# An empty context is used here because we only need to parse the liquid
1515
# string and don't require any additional variables or data.
1616
slug.render(Liquid::Context.new)
17-
end
17+
end
18+
19+
# this is a custom filter used in `person.html` to help with ordering
20+
module Jekyll
21+
module OptechMentionsSortFilter
22+
def sort_by_newsletter_number(array)
23+
array.sort_by { |item| item['newsletter_number'] }
24+
end
25+
end
26+
end
27+
28+
Liquid::Template.register_filter(Jekyll::OptechMentionsSortFilter)

assets/css/main.scss

+23
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,27 @@ div.podcast .anchor-list {
324324
width: 32px;
325325
margin: 2px;
326326
display: inline;
327+
}
328+
329+
/* Backlinks */
330+
331+
.backlink-box {
332+
position: relative;
333+
font-size: 0.9em;
334+
background: #f5f5f5;
335+
border-radius: 4px;
336+
padding-left: 3em;
337+
padding-top: 1.7em;
338+
padding-right: 0.6em;
339+
p {
340+
padding-bottom: 0.4em;
341+
}
342+
}
343+
344+
.backlink-header {
345+
position: absolute;
346+
top: 0.3em;
347+
left: 0.7em;
348+
font-size: 0.85em;
349+
color: #828282;
327350
}

0 commit comments

Comments
 (0)