Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19bee043fc | ||
|
|
6d7456a85b | ||
|
|
a9ce2a2e9a | ||
|
|
fea4e182a5 | ||
|
|
81bb61eb0b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
.DS_Store
|
||||
/dist-env/
|
||||
/env-publish/
|
||||
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
28
Makefile
28
Makefile
@@ -1,21 +1,17 @@
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo 'commands:'
|
||||
@echo ' dist'
|
||||
|
||||
dist-env:
|
||||
@echo Creating virtual environment...
|
||||
@python3 -m venv 'dist-env'
|
||||
@source dist-env/bin/activate && pip install twine
|
||||
|
||||
.PHONY: dist
|
||||
dist: dist-env
|
||||
dist: setup.py lektor_inlinetags.py
|
||||
[ -z "$${VIRTUAL_ENV}" ] # you can not do this inside a virtual environment.
|
||||
rm -rf dist
|
||||
@echo Building...
|
||||
python3 setup.py sdist bdist_wheel
|
||||
@echo
|
||||
rm -rf ./*.egg-info/ ./build/ MANIFEST
|
||||
|
||||
env-publish:
|
||||
@echo Creating virtual environment...
|
||||
@python3 -m venv 'env-publish'
|
||||
@source env-publish/bin/activate && pip install twine
|
||||
|
||||
.PHONY: publish
|
||||
publish: dist env-publish
|
||||
[ -z "$${VIRTUAL_ENV}" ] # you can not do this inside a virtual environment.
|
||||
@echo Publishing...
|
||||
@echo "\033[0;31mEnter your PyPI token:\033[0m"
|
||||
@source dist-env/bin/activate && export TWINE_USERNAME='__token__' && twine upload dist/*
|
||||
@echo "\033[0;31mEnter PyPI token in password prompt:\033[0m"
|
||||
@source env-publish/bin/activate && export TWINE_USERNAME='__token__' && twine upload dist/*
|
||||
|
||||
16
README.md
16
README.md
@@ -7,14 +7,8 @@ Of course, you can configure a different matching pattern, e.g., instead of the
|
||||
|
||||
This plugin is based on the [lektor-groupby](https://github.com/relikd/lektor-groupby-plugin) plugin.
|
||||
All configuration options from groupby can be used (including multiple attribute names).
|
||||
|
||||
|
||||
### Known issues
|
||||
|
||||
In rare cases, clicking on "Save Changes" will not replace the tags of the current page.
|
||||
It happens because the page is built concurrently (race condition).
|
||||
This affects only the currently edited page and only the inline replacements (the tags page is updated just fine).
|
||||
If this occurs to you, simply edit and save the page again.
|
||||
Further, you can access the tags of a page with the filter `|vgroups(key1, key2, recursive=False)` where key is `0..N` attribute keys.
|
||||
If no key is provided, all attributes will be returned – otherwise only matching attribute keys.
|
||||
|
||||
|
||||
### Example config file
|
||||
@@ -37,15 +31,15 @@ In your plugin config (`configs/inlinetags.ini`):
|
||||
```ini
|
||||
[inlinetags]
|
||||
root = /
|
||||
slug = "tag/{}/index.html".format(this.key)
|
||||
slug = tag/{key}/
|
||||
template = tag-page.html
|
||||
|
||||
[inlinetags.pattern]
|
||||
match = {{([^}]{1,32})}}
|
||||
replace = <a href="/tag/{key}/">{name}</a>
|
||||
replace = <a class="tag" href="{url}">{name}</a>
|
||||
|
||||
[inlinetags.fields]
|
||||
title = "Tagged: " ~ this.group
|
||||
title = "Tagged: " ~ this.key_obj
|
||||
|
||||
[inlinetags.key_map]
|
||||
C# = c-sharp
|
||||
|
||||
5
example/Example.lektorproject
Normal file
5
example/Example.lektorproject
Normal file
@@ -0,0 +1,5 @@
|
||||
[project]
|
||||
name = Inlinetags Example
|
||||
|
||||
[packages]
|
||||
lektor-inlinetags = 0.9.2
|
||||
7
example/Makefile
Normal file
7
example/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
.PHONY: server clean plugins
|
||||
server:
|
||||
lektor server
|
||||
clean:
|
||||
lektor clean --yes -v
|
||||
plugins:
|
||||
lektor plugins flush-cache && lektor plugins list
|
||||
23
example/configs/inlinetags.ini
Normal file
23
example/configs/inlinetags.ini
Normal file
@@ -0,0 +1,23 @@
|
||||
[inlinetags]
|
||||
root = /
|
||||
slug = tag/{key}/
|
||||
template = tag-page.html
|
||||
|
||||
[inlinetags.pattern]
|
||||
match = {{([^}]{1,32})}}
|
||||
replace = <a class="tag" href="{url}">{name}</a>
|
||||
|
||||
[inlinetags.fields]
|
||||
title = "Tagged: " ~ this.key_obj
|
||||
|
||||
[inlinetags.key_map]
|
||||
C# = c-sharp
|
||||
|
||||
|
||||
[inlinetags_hidden]
|
||||
slug = hidden-tag/{key}/
|
||||
template = tag-page.html
|
||||
|
||||
[inlinetags_hidden.pattern]
|
||||
match = {{([^}]{1,32})}}
|
||||
replace = {name}
|
||||
3
example/content/contents.lr
Normal file
3
example/content/contents.lr
Normal file
@@ -0,0 +1,3 @@
|
||||
title: Use Inlinetags in {{title}}!
|
||||
---
|
||||
body: This is a {{demo}} {{demo}} website that shows how to use {{Lektor}} with inline {{tags}}. Test {{C#}}.
|
||||
13
example/models/page.ini
Normal file
13
example/models/page.ini
Normal file
@@ -0,0 +1,13 @@
|
||||
[model]
|
||||
name = Page
|
||||
label = {{ this.title }}
|
||||
|
||||
[fields.title]
|
||||
label = Title
|
||||
type = string
|
||||
inlinetags_hidden = true
|
||||
|
||||
[fields.body]
|
||||
label = Body
|
||||
type = markdown
|
||||
inlinetags = true
|
||||
21
example/templates/page.html
Normal file
21
example/templates/page.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Inlinetags</title>
|
||||
<style type="text/css">
|
||||
main { margin: 1em; }
|
||||
footer { padding: 1em; background: #DDD; }
|
||||
</style>
|
||||
<body>
|
||||
<main>
|
||||
{# Using "|safe" to allow clickable links in title #}
|
||||
<h2>{{ this.title|safe }}</h2>
|
||||
{% block body %}{{ this.body }}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<a href="/">Root</a>,
|
||||
Tags:
|
||||
{%- for x in this|vgroups(recursive=True, order_by='key_obj') %}
|
||||
<a href="{{ x|url }}"><{{x.key_obj}}></a>
|
||||
{%- endfor %}
|
||||
</footer>
|
||||
</body>
|
||||
10
example/templates/tag-page.html
Normal file
10
example/templates/tag-page.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "page.html" %}
|
||||
{% block body %}
|
||||
<p>Key: <b>{{this.key}}</b> | Object: <b>{{this.key_obj}}</b></p>
|
||||
Page with <b>{{this.children.count()}}</b> tag(s):
|
||||
<ul>
|
||||
{%- for child in this.children|unique %}
|
||||
<li><a href="{{child|url}}">{{child.title}}</a></li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -1,34 +1,26 @@
|
||||
from lektor.context import get_ctx
|
||||
from lektor.db import Record # typing
|
||||
from lektor.markdown import Markup
|
||||
from lektor.pluginsystem import Plugin, IniFile # subclass
|
||||
from lektor.sourceobj import VirtualSourceObject as VObj # typing
|
||||
|
||||
from typing import Set, Dict, Any, Iterator, Generator
|
||||
from lektor.markdown import Markup # isinstance
|
||||
from lektor.pluginsystem import Plugin # subclass
|
||||
import re
|
||||
from lektor_groupby.groupby import GroupBy # typing
|
||||
from lektor_groupby.util import report_config_error
|
||||
from lektor_groupby.watcher import GroupByCallbackArgs # typing
|
||||
from typing import TYPE_CHECKING, Set, Dict, Any, Generator
|
||||
if TYPE_CHECKING:
|
||||
from lektor.pluginsystem import IniFile
|
||||
from lektor_groupby import GroupBy, GroupByCallbackArgs
|
||||
|
||||
|
||||
class InlineTagsPlugin(Plugin):
|
||||
name = 'inlinetags'
|
||||
description = 'Auto-detect and reference tags inside written text.'
|
||||
|
||||
def on_setup_env(self, **extra: Any) -> None:
|
||||
def _fn(record: Record, *, recursive: bool = False) -> Iterator[VObj]:
|
||||
fn = self.env.jinja_env.filters['vgroups']
|
||||
yield from fn(record, *self.config_keys, recursive=recursive)
|
||||
|
||||
self.env.jinja_env.filters.update(inlinetags=_fn)
|
||||
|
||||
def on_process_template_context(self, context: Dict, **extra: Any) -> None:
|
||||
if hasattr(context.get('this'), '_inlinetag_modified'):
|
||||
ctx = get_ctx()
|
||||
if ctx:
|
||||
ctx.record_dependency(self.config_filename)
|
||||
|
||||
def on_groupby_before_build_all(self, groupby: GroupBy, **ex: Any) -> None:
|
||||
def on_groupby_before_build_all(self, groupby: 'GroupBy', **extra: Any) \
|
||||
-> None:
|
||||
''' lektor-groupby entry point. '''
|
||||
self.config_keys = set() # type: Set[str]
|
||||
config = self.get_config()
|
||||
for sect in config.sections():
|
||||
@@ -37,20 +29,23 @@ class InlineTagsPlugin(Plugin):
|
||||
if self._add(sect, config, groupby):
|
||||
self.config_keys.add(sect)
|
||||
|
||||
def _add(self, sect_key: str, config: IniFile, groupby: GroupBy) -> bool:
|
||||
def _add(self, sect_key: str, config: 'IniFile', groupby: 'GroupBy') \
|
||||
-> bool:
|
||||
''' Parse config section and add callback. Return True on success. '''
|
||||
_pattern = config.section_as_dict(sect_key + '.pattern')
|
||||
regex_str = _pattern.get('match', r'{{([^}]{1,32})}}') # type: str
|
||||
tag_replace = _pattern.get('replace', '{name}') # type: str
|
||||
try:
|
||||
regex = re.compile(regex_str)
|
||||
except Exception as e:
|
||||
report_config_error(sect_key, 'pattern.match', regex_str, e)
|
||||
return False
|
||||
raise ValueError(
|
||||
'Invalid regex patter [{}.{}] = "{}" – Error: {}'.format(
|
||||
sect_key, 'pattern.match', regex_str, repr(e)))
|
||||
|
||||
watcher = groupby.add_watcher(sect_key, config)
|
||||
watcher = groupby.add_watcher(sect_key, config, pre_build=True)
|
||||
|
||||
@watcher.grouping()
|
||||
def _inlinetag(args: GroupByCallbackArgs) -> Generator[str, str, None]:
|
||||
def _fn(args: 'GroupByCallbackArgs') -> Generator[str, str, None]:
|
||||
arr = args.field if isinstance(args.field, list) else [args.field]
|
||||
_tags = {} # type: Dict[str, str]
|
||||
for obj in arr:
|
||||
@@ -59,14 +54,15 @@ class InlineTagsPlugin(Plugin):
|
||||
if isinstance(obj, str) and str:
|
||||
for match in regex.finditer(obj):
|
||||
name = match.group(1)
|
||||
_tags[name] = yield name
|
||||
vobj = yield name
|
||||
_tags[name] = vobj.url_path
|
||||
# ignore other types (int, float, date, url, undefined)
|
||||
|
||||
# replace inline-tags with hyperlink
|
||||
if _tags:
|
||||
def _repl_tags(match: re.Match) -> str:
|
||||
name = match.group(1)
|
||||
return tag_replace.format(key=_tags[name], name=name)
|
||||
return tag_replace.format(url=_tags[name], name=name)
|
||||
|
||||
args.record._inlinetag_modified = True
|
||||
# get field value
|
||||
|
||||
4
setup.py
4
setup.py
@@ -6,7 +6,7 @@ with open('README.md') as fp:
|
||||
setup(
|
||||
name='lektor-inlinetags',
|
||||
py_modules=['lektor_inlinetags'],
|
||||
install_requires=['lektor-groupby>=0.9.5'],
|
||||
install_requires=['lektor-groupby==0.9.8'],
|
||||
entry_points={
|
||||
'lektor.plugins': [
|
||||
'inlinetags = lektor_inlinetags:InlineTagsPlugin',
|
||||
@@ -14,7 +14,7 @@ setup(
|
||||
},
|
||||
author='relikd',
|
||||
url='https://github.com/relikd/lektor-inlinetags-plugin',
|
||||
version='0.9',
|
||||
version='0.9.2',
|
||||
description='Auto-detect and reference tags inside written text.',
|
||||
long_description=longdesc,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
Reference in New Issue
Block a user