Quick links
- A minimal demo site on GitLab (Source)
- Beautiful Hugo pull request 222
- Pre-release notes for this pull request
Motivation
For the mathematical ones, please see my previous post.
As a math student, it’s inefficient to reinvent the wheel like engineering students. Thanks to three existing examples, I had convinced myself that I could bring this to the theme Beautiful Hugo.
-
Zongren’s Hexo theme (worked best)
-
Made Mistakes Jekyll theme
-
Network Hobo’s customization of Beautiful Hugo (inspired by the second one, but contains a logic error)
All three of them follow the double-loop structure suggested by Eduardo Bouças.
Difficulties
The names can be easily confused. For example, parentID
can mean the ID of
the comment to which another comment is replying, as well as the ID of the
subscribed comment. Choosing the right word that brings about readable
code isn’t easy.
Discussion
To increase the chance that my Beautiful Hugo pull request get merged, I’ve explained the code change in details, and written a pre-release note for this PR.
Preliminary step to solve the problem
Variable names like reply_to
, replyTarget
, etc, don’t convey the data
type, whereas fields[parent]
can be
- confused with
options[parent]
in the Hugo theme’s partial layout for Staticman (layouts/partials/staticman-comments
), and - confusing as the jQuery method
parents()
returns multiple parents.
It took me hours to come up with three appropriate words describing the problem: “main comment”, “reply target” and “comment reply”.
With these clear keywords, it’s much easier to decouple the logic of the double loop presented in this Hugo + Staticman nested comments guide on Network Hobo.
Failed partial layout overloading
I tried passing variables from one partial layout to another.
{{ partial "comment-replies" (dict "entryId_parent" $entryId "SiteDataComments_parent" $.Site.Data.comments "parentId" ._id "parentName" .name "context" .) }}
However, {{ $.Scratch.Get "varName"}}
had failed to work. After several
hours of trying, I’ve given up the idea of breaking main comments and comment
replies into two separate partial layouts when I saw the
relevant source code in Network Hobo’s GitHub repo.
Suboptimal code in Beautiful Hugo’s native Staticman support
Go-HTML Template code
When I was trying to rewrite the double-loop in the above linked page on GitHub, I observed that
- Network Hobo’s loop variable
$comments
is shorter and better than Beautiful Hugo’s one$index, $comments
, and - Network Hobo and Beautiful Hugo use
reply_to
andparent
to mean the same thing.
By successive use of git blame
, I’ve found out that the confusing variable
name parent
was introduced by Bruno Adele at commit d68cf2e0. It’s
quite clear that he’s following the double-loop method, but with the inner loop
missing. By tracing back to the first version of layouts/partials /staticman-comments.html
, I confirm that parent
here means a “main comment”,
instead of options[parent]
in the HTML form in Staticman issue 42.
CSS code
I opened Beautiful Hugo issue 221 after testing my system with a definition list.
The cause of the problem is the selector p
at line 10 in
layouts/partials/staticman-comments.html
.
5.staticman-comments .comment-content{
6 border-top: 1px solid #EEEEEE;
7 padding: 4px 0px 30px 0px;
8}
9
10.staticman-comments .comment-content p {
11 padding: 5px 0px 5px 0px;
12 margin: 5px 58px 0px 58px;
13}
My proposed changes
- HTML form: added three fields
-
replyThread
: ID of the main comment (a.k.a. start of the thread)I’ve preferred this over
threadID
because a main post has empty reply target. That’s backward compatible since missing data fields in data files won’t cause an error in Hugo.A submitted POST request would be read like
replyThread: 123...xyz
. This resembles our natural language: this POST requestreply
to theThread
with ID123...xyz
. -
replyID
: ID of the reply target, for backward anchor to reply target in comment reply -
replyName
: author of the reply target -
“Reset” button: reset every field values to the empty string
-
“Submit” button: button text changes to “Submit reply” when the link button “Reply to XYZ” is clicked. This can be undone by clicking the “Reset” button.
-
- Comment display Go-HTML template code
- simplified the start of the loop as
{{ range $comments }}
- renamed
parent
asreplyThread
. - added a Bootstrap link button for reply under each comment
id
: comment ID, transferable toreplyID
when the button is clickedtitle
: ID of its main comment, transferable toreplyThread
when the button is clicked- for a main comment, these two keys have the same value
href
: jumps to the HTML form- translation of UI text “Reply to” in progress
- each comment reply is an HTML sibling of a main comment
- stores values main comment ID as the
Scratch
variableThreadID
for the inner loop (It won’t throw an error despite empty value.) - inserted an inner loop
- iterates over comment reply with
{{ range $comments }}
- operates on the condition that the main comment ID matches
replyThread
- the rest of the inner loop the same as the origin loop, except the
inclusion of an anchor to its reply target.
- icon for “reply to” for i18n.
- a smaller heading between reply comment author and creation date
- Bootstrap link button under comment reply: set above for explanation
- iterates over comment reply with
- simplified the start of the loop as
- jQuery code in
static/js/staticman.js
to handle the logic discussed above-
an offset which is equal to the height of the
navbar
is set, so that thenavbar
won’t cover the anchor targetThe
animation
serves as scrolling to an element with a differentid
specified in the “reply to” button’shref
attribute. -
when the “reset” button is clicked, the three hidden fields in (1) will be set to the empty string
-
no offset for the HTML form since it’s at the bottom
-
- CSS code
-
fixed the above comment rendering error by replacing
p
with*
-
a left margin of 2em for each comment reply
-
a left margin of 58px for each comment timestamp
The gravatar is of height and width 48px with a right margin of 10px floating on the left-hand side. The height of the gravatar is approximate that of two lines (author name and timestamp).
In each comment reply, an additional line (anchor to reply target is inserted in between these two lines. As a result, the timestamp would be left-aligned with the gravatar instead of the two preceeding lines of text.
Expected behavior:
Comment author
Anchor to reply target
Comment timestampObserved behavior:
Comment author
Anchor to reply target
Comment timestampDisqus’s solution:
Comment author ↷ Anchor to reply target
Comment timestampHowever, since the comment author is wrapped by a
h4
element with class namecomment-author
, I’m afraid that changing this would break other parts. Since I’m not expert in web design/programming, it’s better to leave other experts’ work untouched, and add a little bit of basic CSS code to get this fixed.Even though the intended target of this additional left-margin of 58px is the comment timestamp of the comment replies, since the gravatar is floating on the left-hand side, applying this to main comments won’t cause any addition right shift of the comment timestamp in a main comment.
-
reduced the bottom padding from 30px to 4px to accommodate the Bootstrap “reply to” link button
I’ve played with Firefox’s CSS box model in Developer Tools and done the math to find out the suitable dimensions.
-
Lessons learnt
English
Code needs appropriate variable names to be readable. This requires accurate use of vocabulary.
OML
This project should have been finished in one day if I had treated comment data as objects and drawn OML diagrams for them.
Got lost while reading other’s work, it took me three days to fix Network Hobo’s template code and adapt it to my pull request.
jQuery
jQuery provides plenty of convenient functions to accelerate web scripting.
-
basic selection:
$(this)
,$('#id')
,$('.class')
,$('elem')
-
intermediate selection:
$('#id[key="value"]')
$('.class :first-child')
.parent()
: immediate parent.parents()
: a chain of parents.parents(selector)
: select parent(s) according toselector
.siblings()
: works in a similar way as.parents()
, but for tags directly under the same.parent()
- descendant selector:
$('#my_parent .children1')
-
specific selection:
$('input[name="fields[replyID]"]')
,$('a[href^="#"]')
,$(p[title_="foo"])
-
attribute/value getter & setter:
.text()
,mybtn.val('key')
,mylink.attr('href')
-
scrolling:
scrollTop(ycoord)
-
get/set an element’s position relative to the document:
myTag.offset()
returns an object with propertiestop
andleft
. -
to jump to a different
href
when clicking an anchor, use either one of the following methods witha_delay
of at least 200 milliseconds.animate({scrollTop: ycoord}, a_delay)
: smooth transition- JavaScript’s built-in
setTimeout(function(){...}, a_delay)
delay(a_delay)
has to be used in series, likeaction1(...).delay(a_delay).action2(...)
, so it’s more restrictive.
I’ll be glad to know any CSS way to solve this, since a CSS way should be much simpler.
-
strict equality
===
(!==
if negated) for type equality"0" == 0
: true"0" === 0
: false
-
null
vs empty string""
null
: reference doesn’t even exist,null
has neither properties nor methods""
: reference exists, gives a value of""
null.length
gives a type error;"".length
returns zero.
CSS
- specify dimensions:
margin: top right bottom left
clear
vsfloat
: clear horizontal space or not- descendant selector: there’s also a space separating the parent and the children
- universal selector: use a wildcard
*
to select everything. This can be useful to select all children in a block canvas. - margin vs padding: always use margin except for spaces between border and
content
- margin doesn’t belong to the element (relevant for click events)
- padding belongs to the element
- apart from imagining the picture of CSS box model, take two vertically aligned elements. If their margin is 1px, their separation is 1px; if their padding is 1px, their separation is 2px.