Arbitrary Content Tables

Hello everybody!

I’d like to propose a feature which I call Arbitrary Content Tables (I don’t know if there is another, preexisting name for it). However, I think I should start with the use case.

Use Case

TL;DR: I want to create multiple "toc"s with somewhat dynamically added, user defined entries.

I am writing my personal blog, on which I want to publish relatively long texts (2500 words and up). In these texts, I’d like to state my sources with a list of all sources at the end (similarly to how Wikipedia does it). Also, I want to create lists of figures and stuff like that.

It would also be quite nice to be able to refer to things in such lists. This is really nice in LaTeX, because there you can refer to e.g. images with a title you can define.

Implementation Idea

The best method of doing that, which I can think of, is to have Zola keep track of an arbitrary amount of tables that users can freely fill with arbitrary content (hence the name). Later, the user would be able to refer back to previously added items and iterate over it.

This could be done by exposing some new functions to templates (interface expressed in rust, for syntax highlighting):

type Text = Option<String>;
type URL = Option<String>;

/// This adds a new entry to the specified _act_ (arbitrary content table).
///
/// - The key can be used to refer to the entry later.
/// - The text and url can be retrieved later, e.g. to render a list of all entries.
/// - The returned number is the length of the specified _act_ after adding the new entry.
fn act_add_entry(
    table_name: String,
    key: String,
    text: Text,
    url: URL,
) -> usize;

/// This checks if an entry already exists in an _act_.
fn act_has_entry(table_name: String, key: String) -> bool;

/// This gets a whole _act_ (to iterate over it).
fn act_get_table(table_name: String) -> &HashMap<String, (Text, URL)>;

/// This gets a single entry from an _act_ (e.g. to render references in text).
///
/// The returned `usize` would be the index of the entry.
fn act_get_entry(table_name: String, key: String) -> (usize, Text, URL);

Limitation of this approach

As the tables would be built up while rendering, this would make the position of calling act_get_table important (a significant difference to how the toc works). In particular, it would be impossible to render an act in front of the content it is referring to.

However, for the given use case, this would not be a problem. Also, CSS could be used to make the act appear in front of its content.

Other approaches

Doing it in Tera

My first idea was to implement this myself using templates and Tera. However, looking at the tera documentation I was unable to find the required expressions. To me it seems to be not possible to create structures (like an entry from above) or append elements to an array.

Also, I don’t think there is a way to pass variables to shortcodes, which I would also need to do.

Doing it by hand

This could also be done by hand, of course. I think the reasons for why this is a bad idea are obvious, but I will still list a few of them:

  • it is really easy to mess up the numbers of the references (hence the return value for act_add_entry(…)).
  • it is really easy to miss a reference (hence the user defined key).
  • A list needs to be maintained at the bottom, which would need to be updated whenever something changes.
    • Also, the author would need to jump between the bit of the page where the reference is and where the table is.

Other thoughts

  • While this feature would of course increase the complexity of Zola a bit, I think it’d be a really powerful tool. In my mind, the trade-off would be worth it.
  • I’ve looked at the documentations for a few other SSGs and as far as I can tell, nothings seems to have such a feature. It’d be a unique advantage of Zola.

So now my questions are:

  1. Do I miss a better/simpler solution for this use case? (without decreasing functionality)
  2. Would such a feature be accepted into Zola?
  3. Could this feature somehow be improved?
  4. Is there some major issue I can’t see?

If the answer to question 2 is “yes”, I’d be willing to implement this myself and submit it as a PR.

Thank you in advance!

You can do that: Tera

Not possible right now, from where do you want to pass things?

How would that feature look like from the markdown point of view?

Oh, sorry, I overlooked that.

I want to have a table with the sources for all citations on my page at the bottom. However, for more readability of the markdown source, I’d prefer to specify those sources with the actual citation in my pages markdown source.

If I tried to implement something like that with Tera alone, I’d need to do it using shortcodes. There would be a citation shortcode, that renders the citation and appends its source to that table and a citation_table shortcode, which would render all the sources for the citations.

In order to do that, those shortcodes (and the multiple calls of the citation shortcode) would have to communicate with each other. Therefore I’d need to pass the same array to each of them. That, however, is not possible, because I can’t define or use arrays in my markdown source.

With that said, I think that from a user perspective, this would be a far more cumbersome way of achieving my goal than the feature I proposed.

From a markdown point of view, the feature I proposed might just as well not exist. Shortcodes on the other hand could call these functions.

So when writing the content for a page, you’d just use some shortcodes provided by your theme, and if you did that, the themes page template could render the generated table at the bottom of the page.

There would be no need to pass an array to each shortcode. Also, it would not be required to add another shortcode to the bottom in order to render the table.