add examples
This commit is contained in:
5
examples/Examples.lektorproject
Normal file
5
examples/Examples.lektorproject
Normal file
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = GroupBy Examples
|
||||
|
||||
[packages]
|
||||
lektor-groupby = 0.9.1
|
||||
209
examples/README.md
Normal file
209
examples/README.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Usage
|
||||
|
||||
Overview:
|
||||
- the [quick config](#quick-config) example shows how you can use the plugin config to setup a quick and easy tagging system.
|
||||
- the [simple example](#simple-example) goes into detail how this plugin works.
|
||||
- the [advanced example](#advanced-example) touches on the potentials of the plugin.
|
||||
|
||||
|
||||
|
||||
## About
|
||||
|
||||
To use the groupby plugin you have to add an attribute to your model file.
|
||||
In our case you can refer to the `models/page.ini` model:
|
||||
|
||||
```ini
|
||||
[fields.tags]
|
||||
label = Tags
|
||||
type = strings
|
||||
testA = true
|
||||
testB = true
|
||||
|
||||
[fields.body]
|
||||
label = Body
|
||||
type = markdown
|
||||
testC = true
|
||||
```
|
||||
|
||||
We did define three custom attributes `testA`, `testB`, and `testC`.
|
||||
You may add custom attributes to all of the fields.
|
||||
It is crucial that the value of the custom attribute is set to true.
|
||||
The attribute name is later used for grouping.
|
||||
|
||||
|
||||
|
||||
## Quick config
|
||||
|
||||
Relevant files:
|
||||
```
|
||||
configs/groupby.ini
|
||||
templates/example-config.html
|
||||
```
|
||||
|
||||
The easiest way to add tags to your site is by defining the `groupby.ini` config file.
|
||||
|
||||
```ini
|
||||
[testA]
|
||||
root = /
|
||||
slug = config/{group}.html
|
||||
template = example-config.html
|
||||
split = ' '
|
||||
```
|
||||
|
||||
The configuration parameter are:
|
||||
|
||||
1. The section title (`testA`) must be one of the attribute variables we defined in our model.
|
||||
2. The `root` parameter (`/`) is the root page of the groupby.
|
||||
All results will be placed under this directory, e.g., `/tags/tagname/`.
|
||||
If you use `root = /blog`, the results path will be `/blog/tags/tagname/`.
|
||||
The groupby plugin will traverse all sub-pages wich contain the attribute `testA`.
|
||||
3. The `slug` parameter (`config/{group}.html`) is where the results are placed.
|
||||
In our case, the path resolves to `config/tagname.html`.
|
||||
The default value is `{attrib}/{group}/index.html` which would resolve to `testA/tagname/index.html`.
|
||||
4. The `template`parameter (`example-config.html`) is used to render the results page.
|
||||
If no explicit template is set, the default template `groupby-testA.html` will be used.
|
||||
Where `testA` is replaced with whatever attribute you chose.
|
||||
5. The `split` parameter (`' '`) will be used as string delimiter.
|
||||
Fields of type `strings` and `checkboxes` are already lists and don't need splitting.
|
||||
The split is only relevant for fields of type `string` or `text`.
|
||||
These single-line fields are then expanded to lists as well.
|
||||
If you do not provide the `split` option, the whole field value will be used as tagname.
|
||||
|
||||
|
||||
You can have multiple listeners, e.g., one for `/blog/` and another for `/projects/`.
|
||||
Just create as many custom attributes as you like, each having its own section.
|
||||
|
||||
In your template file you have access to the children (pages) and their tags.
|
||||
The emitted `extras` for the child is a list of original tagnames.
|
||||
|
||||
```jinja2
|
||||
{%- for child, extras in this.children.items() %}
|
||||
<li>Page: {{ child.path }}, Tags: {{ extras }}</li>
|
||||
{%- endfor %}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Simple example
|
||||
|
||||
Relevant files:
|
||||
```
|
||||
packages/simple-example/lektor_simple.py
|
||||
templates/example-simple.html
|
||||
```
|
||||
|
||||
```python
|
||||
def on_groupby_after_build_all(self, groupby, builder, **extra):
|
||||
@groupby.watch('/blog', 'testB', slug='simple/{group}/index.html',
|
||||
template='example-simple.html', flatten=True)
|
||||
def convert_simple_example(args):
|
||||
value = args.field # list, since model is 'strings' type
|
||||
for tag in value:
|
||||
yield slugify(tag), {'val': tag, 'tags_in_page': len(value)}
|
||||
|
||||
# page = args.record # extract additional info from source
|
||||
# fieldKey, flowIndex, flowKey = args.key # or get field index
|
||||
# if flowIndex is None:
|
||||
# obj = page[fieldKey]
|
||||
# else:
|
||||
# obj = page[fieldKey].blocks[flowIndex].get(flowKey)
|
||||
```
|
||||
|
||||
This example is roughly equivalent to the config file example.
|
||||
The parameters of the `@groupby.watch` function (`root`, `attribute`, `slug`, `template`) correspond to the same config parameters described above.
|
||||
There is a new `flatten` parameter:
|
||||
|
||||
- Flatten determines how Flow elements are processed.
|
||||
If `False`, the callback function is called once per Flow element.
|
||||
If `True` (default), the callback is called for all Flow-Blocks of the Flow individually.
|
||||
The attribute `testB` can be attached to either the Flow or a Flow-Block regardless.
|
||||
|
||||
The `args` parameter of the `convert_simple_example()` function is a named tuple, it has three attributes:
|
||||
|
||||
1. The `record` points to the `Page` source which contains the tag.
|
||||
2. The `key` tuple `(field-key, flow-index, flow-key)` tells which field is processed.
|
||||
For Flow types, `flow-index` and `flow-key` are set, otherwise they are `None`.
|
||||
3. The `field` value is the content of the processed field.
|
||||
The field value is reoughly equivalent to the following:
|
||||
|
||||
```python
|
||||
args.page[fieldKey].blocks[flowIndex].get(flowKey)
|
||||
```
|
||||
|
||||
The callback body **can** produce groupings but does not have to.
|
||||
If you choose to produce an entry, you have to `yield` a tuple pair of `(groupkey, extra-info)`.
|
||||
`groupkey` is used to combine & cluster pages and must be URL-safe.
|
||||
The `extra-info` is passed through to your template file.
|
||||
You can yield more than one entry per source or filter / ignore pages if you don't yield anything.
|
||||
|
||||
The template file can access and display the `extra-info`:
|
||||
|
||||
```jinja2
|
||||
{%- for child, extras in this.children.items() %}
|
||||
<b>Page: {{ child.title }}<b>
|
||||
<ul>
|
||||
{%- for extra in extras %}
|
||||
<li>Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{%- endfor %}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Advanced example
|
||||
|
||||
Relevant files:
|
||||
```
|
||||
configs/advanced.ini
|
||||
packages/advanced-example/lektor_advanced.py
|
||||
templates/example-advanced.html
|
||||
```
|
||||
|
||||
The following example is similar to the previous one.
|
||||
Except that it loads a config file and replaces in-text occurrences of `{{Tagname}}` with `<a href="/tag/">Tagname</a>`.
|
||||
|
||||
```python
|
||||
def on_groupby_before_build_all(self, groupby, builder, **extra):
|
||||
# load config
|
||||
regex = re.compile(self.get_config().get('match'))
|
||||
# since we load and use a config file, we need to track the dependency
|
||||
@groupby.depends_on(self.config_filename)
|
||||
@groupby.watch('/', 'testC', slug='advanced/{group}/',
|
||||
template='example-advanced.html')
|
||||
def convert_replace_example(args):
|
||||
# args.field assumed to be Markdown
|
||||
obj = args.field.source
|
||||
for match in regex.finditer(obj):
|
||||
tag = match.group(1)
|
||||
yield slugify(tag), tag
|
||||
|
||||
def _fn(match: re.Match) -> str:
|
||||
tag = match.group(1)
|
||||
return f'<a href="/advanced/{slugify(tag)}/">{tag}</a>'
|
||||
args.field.source = regex.sub(_fn, obj)
|
||||
```
|
||||
|
||||
One **important** thing to notice is, we use `on_groupby_before_build_all` to register our callback function.
|
||||
This is required because we would like to modify the source **before** it is written to disk.
|
||||
If you look back to the [simple example](#simple-example), we used `on_groupby_after_build_all` because we did not care when it is executed.
|
||||
Generally, it makes little difference which one you use (`on-after` is likely less busy).
|
||||
Just know that you can process the source before or after it is build.
|
||||
|
||||
For Markdown fields, we can modify the `source` attribute directly.
|
||||
All other field typed need to be accessed via `args.record` key indirection.
|
||||
|
||||
```ini
|
||||
match = {{([^}]{1,32})}}
|
||||
```
|
||||
|
||||
Lastly, the config file contains a regular expression which matches `{{` + any string less than 32 characters + `}}`.
|
||||
Notice, the parenthesis (`()`) will match the inner part but the replace function (`re.sub`) will remove the `{{}}` too.
|
||||
|
||||
If the user changes the regex pattern in the config file, we need to rebuild all tags.
|
||||
For this purpose we need to track changes to the config file.
|
||||
This is done by calling:
|
||||
|
||||
```python
|
||||
@groupby.depends_on(file1, file2, ...)
|
||||
```
|
||||
1
examples/configs/advanced.ini
Normal file
1
examples/configs/advanced.ini
Normal file
@@ -0,0 +1 @@
|
||||
match = {{([^}]{1,32})}}
|
||||
5
examples/configs/groupby.ini
Normal file
5
examples/configs/groupby.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[testA]
|
||||
root = /
|
||||
slug = config/{group}.html
|
||||
template = example-config.html
|
||||
split = ' '
|
||||
9
examples/content/blog/contents.lr
Normal file
9
examples/content/blog/contents.lr
Normal file
@@ -0,0 +1,9 @@
|
||||
_model: blog
|
||||
---
|
||||
title: Blog
|
||||
---
|
||||
tags:
|
||||
|
||||
Directory
|
||||
Blog
|
||||
samegroup
|
||||
9
examples/content/blog/first-post/contents.lr
Normal file
9
examples/content/blog/first-post/contents.lr
Normal file
@@ -0,0 +1,9 @@
|
||||
title: Hello Website
|
||||
---
|
||||
body: This is an example blog post. {{Tag}} in {{blog}}
|
||||
---
|
||||
tags:
|
||||
|
||||
Initial
|
||||
Blog Post
|
||||
samegroup
|
||||
8
examples/content/contents.lr
Normal file
8
examples/content/contents.lr
Normal file
@@ -0,0 +1,8 @@
|
||||
title: GroupBy Examples
|
||||
---
|
||||
body: Main body {{tag}} {{Two}}
|
||||
---
|
||||
tags:
|
||||
|
||||
Root
|
||||
samegroup
|
||||
9
examples/content/projects/contents.lr
Normal file
9
examples/content/projects/contents.lr
Normal file
@@ -0,0 +1,9 @@
|
||||
title: Projects
|
||||
---
|
||||
body:
|
||||
|
||||
This is a list of the projects:
|
||||
|
||||
* Project 1
|
||||
* Project 2
|
||||
* Project 3
|
||||
4
examples/models/blog-post.ini
Normal file
4
examples/models/blog-post.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[model]
|
||||
name = Blog Post
|
||||
inherits = page
|
||||
hidden = yes
|
||||
7
examples/models/blog.ini
Normal file
7
examples/models/blog.ini
Normal file
@@ -0,0 +1,7 @@
|
||||
[model]
|
||||
name = Blog
|
||||
inherits = page
|
||||
hidden = yes
|
||||
|
||||
[children]
|
||||
model = blog-post
|
||||
18
examples/models/page.ini
Normal file
18
examples/models/page.ini
Normal file
@@ -0,0 +1,18 @@
|
||||
[model]
|
||||
name = Page
|
||||
label = {{ this.title }}
|
||||
|
||||
[fields.title]
|
||||
label = Title
|
||||
type = string
|
||||
|
||||
[fields.tags]
|
||||
label = Tags
|
||||
type = strings
|
||||
testA = true
|
||||
testB = true
|
||||
|
||||
[fields.body]
|
||||
label = Body
|
||||
type = markdown
|
||||
testC = true
|
||||
31
examples/packages/advanced-example/lektor_advanced.py
Normal file
31
examples/packages/advanced-example/lektor_advanced.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from lektor.pluginsystem import Plugin
|
||||
from lektor.utils import slugify
|
||||
import re
|
||||
|
||||
|
||||
class AdvancedGroupByPlugin(Plugin):
|
||||
def on_groupby_before_build_all(self, groupby, builder, **extra):
|
||||
# load config
|
||||
regex = self.get_config().get('match')
|
||||
try:
|
||||
regex = re.compile(regex)
|
||||
except Exception as e:
|
||||
print('inlinetags.regex not valid: ' + str(e))
|
||||
return
|
||||
|
||||
# since we load and use a config file, we need to track the dependency
|
||||
@groupby.depends_on(self.config_filename)
|
||||
@groupby.watch('/', 'testC', slug='advanced/{group}/',
|
||||
template='example-advanced.html')
|
||||
def convert_replace_example(args):
|
||||
# args.field assumed to be Markdown
|
||||
obj = args.field.source
|
||||
for match in regex.finditer(obj):
|
||||
tag = match.group(1)
|
||||
yield slugify(tag), tag
|
||||
|
||||
def _fn(match: re.Match) -> str:
|
||||
tag = match.group(1)
|
||||
return f'<a href="/advanced/{slugify(tag)}/">{tag}</a>'
|
||||
args.field.source = regex.sub(_fn, obj)
|
||||
12
examples/packages/advanced-example/setup.py
Normal file
12
examples/packages/advanced-example/setup.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='lektor-advanced',
|
||||
py_modules=['lektor_advanced'],
|
||||
version='1.0',
|
||||
entry_points={
|
||||
'lektor.plugins': [
|
||||
'advanced = lektor_advanced:AdvancedGroupByPlugin',
|
||||
]
|
||||
}
|
||||
)
|
||||
24
examples/packages/simple-example/lektor_simple.py
Normal file
24
examples/packages/simple-example/lektor_simple.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from lektor.pluginsystem import Plugin
|
||||
from lektor.utils import slugify
|
||||
|
||||
|
||||
class SimpleGroupByPlugin(Plugin):
|
||||
def on_groupby_after_build_all(self, groupby, builder, **extra):
|
||||
@groupby.watch('/blog', 'testB', slug='simple/{group}/index.html',
|
||||
template='example-simple.html', flatten=True)
|
||||
def convert_simple_example(args):
|
||||
# Yield groups
|
||||
value = args.field # list type since model is 'strings' type
|
||||
for tag in value:
|
||||
yield slugify(tag), {'val': tag, 'tags_in_page': len(value)}
|
||||
# Everything below is just for documentation purposes
|
||||
page = args.record # extract additional info from source
|
||||
fieldKey, flowIndex, flowKey = args.key # or get field index
|
||||
if flowIndex is None:
|
||||
obj = page[fieldKey]
|
||||
else:
|
||||
obj = page[fieldKey].blocks[flowIndex].get(flowKey)
|
||||
print('page:', page)
|
||||
print(' obj:', obj)
|
||||
print()
|
||||
12
examples/packages/simple-example/setup.py
Normal file
12
examples/packages/simple-example/setup.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='lektor-simple',
|
||||
py_modules=['lektor_simple'],
|
||||
version='1.0',
|
||||
entry_points={
|
||||
'lektor.plugins': [
|
||||
'simple = lektor_simple:SimpleGroupByPlugin',
|
||||
]
|
||||
}
|
||||
)
|
||||
4
examples/templates/blog-post.html
Normal file
4
examples/templates/blog-post.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% extends "page.html" %}
|
||||
{% block body %}
|
||||
{{ this.body }}
|
||||
{% endblock %}
|
||||
6
examples/templates/blog.html
Normal file
6
examples/templates/blog.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "page.html" %}
|
||||
{% block body %}
|
||||
{% for child in this.children %}
|
||||
<a href="{{child|url}}">{{ child.title }}</a>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
3
examples/templates/example-advanced.html
Normal file
3
examples/templates/example-advanced.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<h2>Path: {{ this | url(absolute=True) }}</h2>
|
||||
<div>This is: {{this}}</div>
|
||||
<div>Children: {{this.children}}</div>
|
||||
7
examples/templates/example-config.html
Normal file
7
examples/templates/example-config.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<h2>Path: {{ this | url(absolute=True) }}</h2>
|
||||
<div>This is: {{this}}</div>
|
||||
<ul>
|
||||
{%- for child, extras in this.children.items() %}
|
||||
<li>Page: {{ child.path }}, Tags: {{ extras }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
12
examples/templates/example-simple.html
Normal file
12
examples/templates/example-simple.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<h2>Path: {{ this | url(absolute=True) }}</h2>
|
||||
<div>This is: {{this}}</div>
|
||||
<ul>
|
||||
{%- for child, extras in this.children.items() %}
|
||||
<li>Page: {{ child.path }}</li>
|
||||
<ul>
|
||||
{%- for extra in extras %}
|
||||
Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
37
examples/templates/page.html
Normal file
37
examples/templates/page.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}Welcome{% endblock %}</title>
|
||||
<style type="text/css">
|
||||
header, footer { padding: 1em; background: #DDD; }
|
||||
main { margin: 3em; }
|
||||
</style>
|
||||
<body>
|
||||
<header>
|
||||
<div>Nav:
|
||||
<a href="{{ '/'|url }}">Root</a>
|
||||
<a href="{{ '/blog'|url }}">Blog</a>
|
||||
<a href="{{ '/projects'|url }}">Projects</a>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h2>{{ this.title }}</h2>
|
||||
{% block body %}{{ this.body }}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<div>Simple Tags:
|
||||
{% for tag in ['blog','directory','blog-post','initial','samegroup'] %}
|
||||
<a href="/blog/simple/{{tag}}/">({{tag}})</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>Config Tags:
|
||||
{% for tag in ['root','blog','directory','blog-post','initial','samegroup'] %}
|
||||
<a href="/config/{{tag}}.html">({{tag}})</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>Advanced Tags:
|
||||
{% for tag in ['tag','two','blog'] %}
|
||||
<a href="/advanced/{{tag}}/">({{tag}})</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
Reference in New Issue
Block a user