Trouble with hooks for a custom backlink generator

I’m building a Jekyll site at https://github.com/steinea/garden, using Maxime Vaillancourt’s Digital Garden Jekyll Template. It’s got a cool backlinks generator plugin that I have successfully tweaked to work with my site config. However, the links generated by a Liquid {% for %} loop are not recognized by the plugin.

I’ve done some reading of the Jekyll documentation, and it appears that this is due to the sequence of the Jekyll rendering process. Maxime’s plugin is a generator which, according to the docs:

run[s] after Jekyll has made an inventory of the existing content, and before the site is generated

This means that the backlinks generator runs before the rendering process, so the links generated by the for loop do not yet exist when the backlink generator runs.

I found the documentation for Jekylls hooks but they’re not the easiest to understand. These seem to be the way to change the sequence in which plugins run in the rendering process, but I haven’t had any success with the variations I’ve tried.

So far, I’ve tried wrapping Maxime’s plugin in a few different variations of the example from the documentation. I think this is what it should be, but it’s still not working:

# BEGIN HOOK

Jekyll::Hooks.register :documents, :post_convert do |docs|

# BEGIN GENERATOR

  # frozen_string_literal: true
  class BidirectionalLinksGenerator < Jekyll::Generator
    def generate(site)
      graph_nodes = []
      graph_edges = []

      all_posts = site.collections['posts'].docs
      all_pages = site.collections['pages'].docs

      all_docs = all_posts + all_pages

      link_extension = !!site.config["use_html_extension"] ? '.html' : ''

       # Identify note backlinks and add them to each note
      all_docs.each do |current_note|
        # Nodes: Jekyll
        notes_linking_to_current_note = all_docs.filter do |e|
          e.url != current_note.url && e.content.include?(current_note.url)
        end

        # Nodes: Graph
        graph_nodes << {
          id: note_id_from_note(current_note),
          path: "#{site.baseurl}#{current_note.url}#{link_extension}",
          label: current_note.data['title'],
        } unless current_note.path.include?('_notes/index.html')

        # Edges: Jekyll
        current_note.data['backlinks'] = notes_linking_to_current_note

        # Edges: Graph
        notes_linking_to_current_note.each do |n|
          graph_edges << {
            source: note_id_from_note(n),
            target: note_id_from_note(current_note),
          }
        end
      end

      File.write('_includes/notes_graph.json', JSON.dump({
        edges: graph_edges,
        nodes: graph_nodes,
      }))
    end

    def note_id_from_note(note)
      note.data['title'].bytes.join
    end
  end
# END GENERATOR

end
# END HOOK

I’ve tried some different registers and some different events, but none of combinations I’ve tried have successfully run the generator after Jekyll has iterated through the for loops on my site and before Jekyll has rendered my site pages. Help?

If I add Jekyll::Hooks.register :documents, :pre_render do |docs| in the plugin file, like so…

# frozen_string_literal: true
class BidirectionalLinksGenerator < Jekyll::Generator
  def generate(site)
    Jekyll::Hooks.register :documents, :pre_render do |docs|
...
end

… the plugin will generate backlinks, but it seems to be capturing too many backlinks, i.e., it’s going to a second layer of depth (e.g. PAGE 1 is linked by PAGE 2, but then PAGE 2 is linked by PAGE 3, and a backlink for PAGE 3 is showing up on PAGE 1, which isn’t correct).

If I use the :post_convert or :post_render events, I get the same result with backlinks, but I also get connections in the notes_graph.json. However, the connections in the graph are also too many, possibly re-linking the backlinks?

So, simply adding the hook register at the top of the plugin is not the correct solution.

Maybe you should use the priority attribute. Give a look at this answer.