Nested Comments in Beautiful Hugo

  1. A minimal demo site on GitLab (Source)
  2. Beautiful Hugo pull request 222
  3. 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.

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

  1. confused with options[parent] in the Hugo theme’s partial layout for Staticman (layouts/partials/staticman-comments), and
  2. 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”.

problem OO sketch

Nested comments data fields

A sketch of the object models of the comment classes

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

  1. Network Hobo’s loop variable $comments is shorter and better than Beautiful Hugo’s one $index, $comments, and
  2. Network Hobo and Beautiful Hugo use reply_to and parent 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.

beautiful hugo issue 221

Beautiful Hugo issue 221

The paragraph and the definition list weren’t properly aligned.

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

  1. 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 request reply to the Thread with ID 123...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.

  2. Comment display Go-HTML template code

    • simplified the start of the loop as {{ range $comments }}
    • renamed parent as replyThread.
    • added a Bootstrap link button for reply under each comment
      • id: comment ID, transferable to replyID when the button is clicked
      • title: ID of its main comment, transferable to replyThread 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 variable ThreadID 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
  3. 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 the navbar won’t cover the anchor target

      The animation serves as scrolling to an element with a different id specified in the “reply to” button’s href 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

  4. 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

      beautiful hugo issue 221 resolved

      Beautiful Hugo issue 221 resolved

      The paragraph and the definition list are now properly aligned.

      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 timestamp

      Observed behavior:

           Comment author
           Anchor to reply target
      Comment timestamp

      Disqus’s solution:

           Comment author ↷ Anchor to reply target
           Comment timestamp

      However, since the comment author is wrapped by a h4 element with class name comment-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 to selector
    • .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 properties top and left.
  • to jump to a different href when clicking an anchor, use either one of the following methods with a_delay of at least 200 milliseconds.

    1. animate({scrollTop: ycoord}, a_delay): smooth transition
    2. JavaScript’s built-in setTimeout(function(){...}, a_delay)
    3. delay(a_delay) has to be used in series, like action1(...).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 vs float: 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.

No comment