dairiki / lektor-index-pages

Lektor plugin to generate blog-like index pages
MIT License
3 stars 0 forks source link

Limitations #3

Open relikd opened 2 years ago

relikd commented 2 years ago

Hi there 👋

So, I've had time to look at your plugin:

  1. It seems the key value is called multiple times, could this be cached? e.g., in my case key = item|test('a') will call the filter three times for a single page

  2. Could you add a way to attach some data to the key? For example, I would like to define a different slug for a category name. Here is a simple use case: key = ['C#', 'C']. In both cases the slug will be c and the results overwrite each other.

relikd commented 2 years ago

Also, previous pages are not pruned when its source changes -> PR #4

dairiki commented 2 years ago

It seems the key value is called multiple times, could this be cached?

Yes, it could be. One straight-forward (I think), but hackish way would be to use a WeakKeyDictionary to memoize either IndexModel.keys_for_post (or maybe FieldDescriptor.__get__).

I'm dubious that it will make much difference in total build time in most cases. Do you have a particular use case where it does?


Could you add a way to attach some data to the key? For example, I would like to define a different slug for a category name. Here is a simple use case: key = ['C#', 'C']. In both cases the slug will be c and the results overwrite each other.

Particularly if you only have a finite set of keys, one Lektor-ish way to associate arbitrary metadata with keys is to create a Lektor "page" with subpages for each category. E.g.

models/languages.ini:

[model]
name = Programming Languages
label = {{ this.name }}
hidden = yes
protected = yes

[children]
model = language
order_by = name

models/language.ini:

[model]
name = Programming Language
label = {{ this.name }}
hidden = yes

content/languages/contents.lr:

_model: languages
---
title: Programming Languages

content/languages/c/contents.lr:

title: C

content/languages/cpp/contents.lr:

title: C++

In your blog post model:

...
[fields.languages]
label = Languages
type = checkboxes
source = site.query('/languages')
...

(or use type = select if only one language is associated with each post.)

Then, in templates:

{% set languages = site.query('/languages') %}
{% for idx in index_pages('languages') %}
  {% set lang = languages.get(idx.key) %}
  <li><a href="{{ idx|url }}">{{ lang.title }}<a></li>
{% endfor %}

If that doesn't work for you, I suppose something could be added, but I'm not sure what the API should look like. One could add a config option to allow for a custom slugification function, but that seems clumsy to use.

relikd commented 2 years ago

I'm dubious that it will make much difference in total build time in most cases. Do you have a particular use case where it does?

It depends on what you do with it. For normal field types it should make no difference. But as soon as you start piping the results into a custom function it depends on the complexity of that function. In my case, applying a regex search on all record fields. Without a key cache it would perform the search again and again.

Particularly if you only have a finite set of keys, one Lektor-ish way to associate arbitrary metadata with keys is to create a Lektor "page" with subpages for each category.

I am not sure your example fits. First, no, the set of keys is not finite. In my case these are just two tags I might use for my "projects" page. One project might have the tags C#, Game, and another project might have the tags C and CLI. I do not want to create subpages for all possible tags I might use but rather generate the pages dynamically. Think of it this way: I have a strings field which contains the tags and is also used to display the tags in the projects overview page. I dont know what the values will be but I want all of them to reference the according index page.

If that doesn't work for you, I suppose something could be added, but I'm not sure what the API should look like. One could add a config option to allow for a custom slugification function, but that seems clumsy to use.

The interesting thing would be to pass additional data to the template. You could, for example, differentiate if the return value is a str, list of str, or list of tuples. So, with a custom keying function, instead of returning ['C', 'C#'], I could return [('c', 'C'), ('c-sharp', 'C#')]. Where the first value is used as slug and the latter is passed to the template where I can use it for, e.g., a title heading. And I wouldn't limit it to strings. Imagine I, as a developer, can return [('category', {'author': 'me', 'date': datetime, ...}), ...].

But then again, what happens with inconsitencied? What if one page returns ('c', 'C') and another ('c', 'Plain C')? Maybe the extra data is not attached to the IndexPage but rather to the child that generated the key? At which point I can add the extra data directly to the page record, either as attribute or in record._data.

Hmm, maybe a custom slugify is enough. Or have a config option for a key -> slug mapping. In my plugin I use this:

# somewhere in on_setup_env or reloaded every build
slug_map = config.section_as_dict('slug_map')
# somewhere later in the virtual object path
slug = slug_map.get(title, slugify(title))