@ -0,0 +1,19 @@ |
|||
Title: First batch |
|||
Date: 2019-05-14 |
|||
Category: News |
|||
Summary: On the 5th of April 2019 we brewed the first Multilaterale [brew]({filename}redale.md) at the [Noord-Hollandse Bierbrouwerij](http://www.dnhbb.nl/dnhbb/). |
|||
|
|||
On the 5th of April 2019 we brewed the first Multilaterale [brew]({filename}redale.md) at the [Noord-Hollandse Bierbrouwerij](http://www.dnhbb.nl/dnhbb/). |
|||
|
|||
![]({attach}images/bottling.jpeg) |
|||
|
|||
We bottled 3528 bottles on the 14th of May. |
|||
|
|||
![]({attach}images/delivery.jpeg) |
|||
|
|||
We received this on the 29th of May. |
|||
|
|||
The first batch also featured a special design for Goethe Institut Niederlande. |
|||
|
|||
![]({attach}images/redale.jpg) |
|||
![]({attach}images/goethe.jpeg) |
@ -0,0 +1,16 @@ |
|||
Title: Ongoing specials for Goethe-Institut NL |
|||
Date: 2020-02-11 |
|||
Category: News |
|||
Summary: This is already our second special edition made for the Goethe-Institut Netherlands. |
|||
|
|||
For every batch that we brew we ask our stakeholders if they are interested to have their own label for the upcoming batch. This is already the second time we worked together with Goethe-Institut Netherlands to design and produce their own label!. |
|||
|
|||
## GI Pale Ale |
|||
![]({attach}/images/goethe_paleale.jpeg) |
|||
|
|||
## GI Red Ale |
|||
![]({attach}/images/goethe.jpeg) |
|||
|
|||
## Interested? |
|||
|
|||
If you are intersted in doing the same, get in touch! |
@ -0,0 +1,17 @@ |
|||
Title: Golden Dawn |
|||
Date: 2020-11-03 |
|||
Category: Brews |
|||
Image: images/web96_igfm_1.jpg |
|||
Summary: Golden Dawn for Murder Capital |
|||
|
|||
A Golden Ale made for [Intergalactic.fm](https://intergalactic.fm). Fresh with a moderate bitterness. Hopped with Magnum and Ariana. |
|||
|
|||
![]({static}images/web96_igfm_1.jpg) |
|||
|
|||
###Data |
|||
|
|||
* IBU: 21 |
|||
* EBC: 9 |
|||
* ABV: 4.1% |
|||
* TOP fermentation |
|||
|
@ -0,0 +1,18 @@ |
|||
Title: Irish Stout |
|||
Date: 2020-07-29 |
|||
Category: Brews |
|||
Summary: Irish Stout in collaboration with Brouwerij Groos |
|||
Image: images/Stout.jpg |
|||
|
|||
This Irish Stout came to be in collaboration with [Brouwerij Groos](https://untappd.com/BrouwerijGroos). It has a souple body, roasty notes of coffee and chocolate. Already sold out and here mostly for archival purposes. |
|||
|
|||
![]({static}images/Stout.jpg) |
|||
|
|||
|
|||
###Data |
|||
|
|||
* IBU: 24 |
|||
* EBC: 75 |
|||
* ABV: 4.5% |
|||
* TOP fermentation |
|||
|
After Width: | Height: | Size: 479 KiB |
After Width: | Height: | Size: 492 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 133 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 302 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 80 KiB |
@ -0,0 +1,19 @@ |
|||
Title: About |
|||
Date: 2019-01-01 |
|||
|
|||
### About |
|||
|
|||
Multilaterale is a brewing initiative by and for the independent art spaces in Rotterdam.The idea is that the Multilaterale Group can brew high quality beers of styles otherwise not commonly found, while the spaces in the Multilaterale network can use the sales to finance their activities. With this model we hope to sustain both independent spaces and community brewing initiatives. |
|||
|
|||
### Support |
|||
|
|||
Rather than purchasing the brews after production, the spaces in the network order in advance. This minimizes financial risks for the brewers, which allows for more creativity on the one hand and guarantees good wholesale prices for the network on the other hand. This way Multilaterale is also an invitation to the network to participate in and think about the brewing processes, logistics, design and mutual support. |
|||
|
|||
### Who? |
|||
|
|||
Multilaterale is an initiative by Henning Rosenbrock and Roel Roscam Abbing who are also founding members of the [Brouwvereniging Rotterdam](https://brouwvereniging.nl), the home brewing association of Rotterdam. |
|||
|
|||
|
|||
### Contact |
|||
|
|||
You can reach us via cheers[at]multilaterale.group! |
@ -0,0 +1,62 @@ |
|||
Title: Network |
|||
Date: 2019-01-01 |
|||
|
|||
Currently the Multilaterale network is composed of the following spaces. These are also the locations where you can purchase Multilaterale. You can also contact us to purchase directly. |
|||
|
|||
###Multilaterale Group |
|||
https://multilaterale.group |
|||
|
|||
Get in touch if you have a party, an opening, a presentation or in need of a gift. Drop us a mail at cheers[at]multilaterale.group |
|||
|
|||
### varia |
|||
Gouwstraat 3 Rotterdam | <https://varia.zone> |
|||
|
|||
varia is a space for developing collective approaches to everyday technology. |
|||
|
|||
Multilaterale beers are available during the events. |
|||
|
|||
### DE PLAYER |
|||
Hillelaan 49d Rotterdam | <https://www.deplayer.nl/> |
|||
|
|||
|
|||
DE PLAYER is a polymorhpic production platform in Rotterdam (nl), which has been putting together programs on the cutting edge of performance art, experimental music and the visual arts. |
|||
|
|||
Multilaterale was available during Gardena, we don't know wheter they still have stock. |
|||
|
|||
### Stichting AppendiX Grafiek |
|||
<http://www.appendix-grafiek.nl> |
|||
|
|||
AppendiX is a Rotterdam based platform for contemporary print, graphics and fine arts. |
|||
|
|||
Multilaterale beers are available during the events. |
|||
|
|||
|
|||
|
|||
### Goethe Institut Niederlande |
|||
Westersingel 9 Rotterdam en Herengracht 470 Amsterdam | <https://www.goethe.de/nederland> |
|||
|
|||
Multilaterale beers were available during the events, but we don't know whether they still have stock. |
|||
|
|||
### ook_huis |
|||
Brielselaan 198 Rotterdam | <http://ook.website/> |
|||
|
|||
ook_huis can also be used freely by neighbors and acquaintances. |
|||
By programming various activities, ook_ wants to show a communal presence: just meeting eachother. |
|||
It is a home for also_artistic activity, in whatever way. |
|||
|
|||
Sometimes there is also Multilaterale. |
|||
|
|||
###Shimmer |
|||
Waalhaven Oostzijde 1 Rotterdam | <http://shimmershimmer.org> |
|||
|
|||
Shimmer is a Rotterdam based exhibition space that operates with an ever-changing studio-like mentality where knowledge arises through participation and experimentation. |
|||
|
|||
Multilaterale beers are available during openings. |
|||
|
|||
### LAG |
|||
1e Schinkelstraat 14-16 Amsterdam | <https://laglab.org/> |
|||
|
|||
LAG is: |
|||
onsidering hacking a political practice. We give workshops and learn how to have difficulties finding where people to challenging environment where people to challenging environment away from one is free to create and we want to be more their technology an |
|||
|
|||
They drank all the Multilaterale and we are currently waiting to hear if they want more! |
@ -0,0 +1,39 @@ |
|||
Title: Pale Ale |
|||
Date: 2020-02-11 |
|||
Category: Brews |
|||
Image: images/paleale.jpg |
|||
|
|||
A new brew has arrived! A hoppy, fruity Pale Ale this time! |
|||
|
|||
Brewed with Cascade, Hersbrucker and Aurora hops this refreshing light-colored beer will leave you wanting for more! Since this is a smaller batch, make sure to get it while it lasts! |
|||
|
|||
The perfect beer for all those who ask you 'Do you also have something special?'. |
|||
|
|||
This ale was brewed in collaboration with our friends at Groos brewery. |
|||
|
|||
![]({static}images/paleale.jpg) |
|||
|
|||
###Data |
|||
|
|||
* IBU: 23 |
|||
* EBC: 12 |
|||
* ABV: 5.3% |
|||
* TOP fermentation |
|||
|
|||
### Malts |
|||
|
|||
* Barley |
|||
* Oats |
|||
|
|||
### Hops |
|||
|
|||
* Cascade |
|||
* Hersbrucker |
|||
* Aurora |
|||
|
|||
<br> |
|||
|
|||
![]({attach}images/Multilaterale_Pale_3.jpg) |
|||
|
|||
Right off the bottling line! |
|||
|
@ -0,0 +1,19 @@ |
|||
Title: Special Editions for Murder Capital |
|||
Date: 2020-07-29 |
|||
Category: News |
|||
Summary: Panama Racing Club special editions. |
|||
|
|||
Our Red Ale and Irish Stout have been labelled in a Panama Racing Club theme for Murder Capital label. The Panama Red and Night Rider. Check them out in the IFM webshop: https://www.intergalactic.fm/shop/main/murdercapital-beer/?v=7516fd43adaa |
|||
|
|||
## Here they are |
|||
![]({attach}/images/MC.Beer.jpg) |
|||
|
|||
![]({attach}/images/MC.kit.jpg) |
|||
|
|||
## Cheers |
|||
|
|||
![]({attach}/images/cheerswference.jpg) |
|||
|
|||
## Interested? |
|||
|
|||
If you are intersted in doing the same, get in touch! |
@ -0,0 +1,37 @@ |
|||
Title: Red Ale |
|||
Date: 2019-05-14 |
|||
Category: Brews |
|||
Image: images/redale.jpg |
|||
|
|||
Our first Multilaterale Group beer is a Red Ale. Inspired by Irish Red Ales. |
|||
|
|||
It has a vibrant copper red colour. Smooth and malty flavour with tender hoppiness and a slight hint of sour in the after taste. |
|||
|
|||
The perfect beer for all those who ask you 'Do you also have normal beer?'. |
|||
|
|||
![]({static}images/redale.jpg) |
|||
|
|||
###Data |
|||
|
|||
* IBU: 18 |
|||
* EBC: 22 |
|||
* ABV: 4.7% |
|||
* TOP fermentation |
|||
|
|||
### Malts |
|||
|
|||
* 65% Pale Ale |
|||
* 24% Cara Red |
|||
* 12% Cara 120 |
|||
|
|||
### Hops |
|||
|
|||
* Magnum |
|||
* Fuggle |
|||
* Bramling Cross |
|||
|
|||
<br> |
|||
|
|||
![]({attach}images/skiddaw.jpg) |
|||
|
|||
We have evidence of an Irishman approving of our Red Ale. This obviously bespeaks of the quality of this beer. |
@ -0,0 +1,17 @@ |
|||
Title: Session Pale |
|||
Date: 2020-11-03 |
|||
Category: Brews |
|||
Image: images/web96_session_pale.jpg |
|||
Summary: Session Pale Ale |
|||
|
|||
New Session Pale Ale from yours truly. Fruity, hoppy with a grapefruit-like bitterness. Hopped with Magnum and Cascade. |
|||
|
|||
![]({static}images/web96_session_pale.jpg) |
|||
|
|||
###Data |
|||
|
|||
* IBU: 30 |
|||
* EBC: 10 |
|||
* ABV: 4.1% |
|||
* TOP fermentation |
|||
|
@ -0,0 +1,41 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- # |
|||
from __future__ import unicode_literals |
|||
|
|||
AUTHOR = 'the group' |
|||
SITENAME = 'MULTILATERALE' |
|||
SITEURL = 'https://multilaterale.group' |
|||
|
|||
PATH = 'content' |
|||
|
|||
TIMEZONE = 'Europe/Paris' |
|||
|
|||
DEFAULT_LANG = 'en' |
|||
DEFAULT_DATE = 'fs' |
|||
|
|||
THEME = "themes/multilaterale/" |
|||
|
|||
PLUGIN_PATHS = ['plugins'] |
|||
PLUGINS = ["representative_image"] |
|||
|
|||
# Feed generation is usually not desired when developing |
|||
FEED_ALL_ATOM = None |
|||
CATEGORY_FEED_ATOM = None |
|||
TRANSLATION_FEED_ATOM = None |
|||
AUTHOR_FEED_ATOM = None |
|||
AUTHOR_FEED_RSS = None |
|||
|
|||
# Blogroll |
|||
LINKS = (('Pelican', 'http://getpelican.com/'), |
|||
('Python.org', 'http://python.org/'), |
|||
('Jinja2', 'http://jinja.pocoo.org/'), |
|||
('You can modify those links in your config file', '#'),) |
|||
|
|||
# Social widget |
|||
SOCIAL = (('You can add links in your config file', '#'), |
|||
('Another social link', '#'),) |
|||
|
|||
DEFAULT_PAGINATION = False |
|||
|
|||
# Uncomment following line if you want document-relative URLs when developing |
|||
#RELATIVE_URLS = True |
@ -0,0 +1 @@ |
|||
from .representative_image import * |
@ -0,0 +1,50 @@ |
|||
import six |
|||
from pelican import signals |
|||
from pelican.contents import Article, Page |
|||
from pelican.generators import ArticlesGenerator |
|||
from bs4 import BeautifulSoup |
|||
|
|||
|
|||
def images_extraction(instance): |
|||
representativeImage = None |
|||
if type(instance) in (Article, Page): |
|||
if 'image' in instance.metadata: |
|||
representativeImage = instance.metadata['image'] |
|||
|
|||
# Process Summary: |
|||
# If summary contains images, extract one to be the representativeImage and remove images from summary |
|||
soup = BeautifulSoup(instance.summary, 'html.parser') |
|||
images = soup.find_all('img') |
|||
for i in images: |
|||
if not representativeImage: |
|||
representativeImage = i['src'] |
|||
i.extract() |
|||
if len(images) > 0: |
|||
# set _summary field which is based on metadata. summary field is only based on article's content and not settable |
|||
instance._summary = six.text_type(soup) |
|||
|
|||
# If there are no image in summary, look for it in the content body |
|||
if not representativeImage: |
|||
soup = BeautifulSoup(instance._content, 'html.parser') |
|||
imageTag = soup.find('img') |
|||
if imageTag: |
|||
representativeImage = imageTag['src'] |
|||
|
|||
# Set the attribute to content instance |
|||
instance.featured_image = representativeImage |
|||
|
|||
|
|||
def run_plugin(generators): |
|||
for generator in generators: |
|||
if isinstance(generator, ArticlesGenerator): |
|||
for article in generator.articles: |
|||
images_extraction(article) |
|||
|
|||
|
|||
def register(): |
|||
try: |
|||
signals.all_generators_finalized.connect(run_plugin) |
|||
except AttributeError: |
|||
# NOTE: This results in #314 so shouldn't really be relied on |
|||
# https://github.com/getpelican/pelican-plugins/issues/314 |
|||
signals.content_object_init.connect(images_extraction) |
@ -0,0 +1,33 @@ |
|||
Thumbnail Creation of images |
|||
============================ |
|||
|
|||
This plugin creates thumbnails for all of the images found under a specific directory, in various thumbnail sizes. |
|||
It requires `PIL` to function properly since `PIL` is used to resize the images, and the thumbnail will only be re-built |
|||
if it doesn't already exist (to save processing time). |
|||
|
|||
Installation |
|||
------------- |
|||
|
|||
Set up like any other plugin, making sure to set `PLUGIN_PATH` and add `thumbnailer` to the `PLUGINS` list. |
|||
|
|||
[PIL](http://www.pythonware.com/products/pil/) or [Pillow](http://pillow.readthedocs.org/en/latest/installation.html#) |
|||
is required. If you choose Pillow, you need to install some additional |
|||
[external libraries](http://www.pythonware.com/products/pil/) to add support for image types like `jpg`. |
|||
|
|||
Configuration |
|||
------------- |
|||
|
|||
* `IMAGE_PATH` is the path to the image directory. It should reside inside your content directory, and defaults to "pictures". |
|||
* `THUMBNAIL_DIR` is the path to the output sub-directory where the thumbnails are generated. |
|||
* `THUMBNAIL_SIZES` is a dictionary mapping size name to size specifications. |
|||
The generated filename will be `originalname_thumbnailname.ext` unless `THUMBNAIL_KEEP_NAME` is set. |
|||
* `THUMBNAIL_KEEP_NAME` is a Boolean that, if set, puts the file with the original name in a thumbnailname folder, named like the key in `THUMBNAIL_SIZES`. |
|||
* `THUMBNAIL_KEEP_TREE` is a Boolean that, if set, saves the image directory tree. |
|||
* `THUMBNAIL_INCLUDE_REGEX` is an optional string that is used as regular expression to restrict thumbnailing to matching files. By default all files not starting with a dot are respected. |
|||
|
|||
Sizes can be specified using any of the following formats: |
|||
|
|||
* wxh will resize to exactly wxh cropping as necessary to get that size |
|||
* wx? will resize so that the width is the specified size, and the height will scale to retain aspect ratio |
|||
* ?xh same as wx? but will height being a set size |
|||
* s is a shorthand for wxh where w=h |
@ -0,0 +1 @@ |
|||
from .thumbnailer import * |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 330 KiB |
@ -0,0 +1,63 @@ |
|||
from thumbnailer import _resizer |
|||
from unittest import TestCase, main |
|||
import os |
|||
from PIL import Image |
|||
|
|||
class ThumbnailerTests(TestCase): |
|||
|
|||
def path(self, filename): |
|||
return os.path.join(self.img_path, filename) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
self.img = Image.open(self.path("sample_image.jpg")) |
|||
|
|||
def testSquare(self): |
|||
r = _resizer('square', '100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((100, 100), output.size) |
|||
|
|||
def testExact(self): |
|||
r = _resizer('exact', '250x100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 100), output.size) |
|||
|
|||
def testWidth(self): |
|||
r = _resizer('aspect', '250x?', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 166), output.size) |
|||
|
|||
def testHeight(self): |
|||
r = _resizer('aspect', '?x250', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((375, 250), output.size) |
|||
|
|||
class ThumbnailerFilenameTest(TestCase): |
|||
|
|||
def path(self, *parts): |
|||
return os.path.join(self.img_path, *parts) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
|
|||
def testRoot(self): |
|||
"""Test a file that is in the root of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testRootWithSlash(self): |
|||
r = _resizer('square', '100', self.img_path + '/') |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testSubdir(self): |
|||
"""Test a file that is in a sub-directory of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('subdir', 'sample_image.jpg')) |
|||
self.assertEqual('subdir/sample_image_square.jpg', new_name) |
|||
|
|||
if __name__=="__main__": |
|||
main() |
@ -0,0 +1,210 @@ |
|||
import os |
|||
import os.path as path |
|||
import re |
|||
from pelican import signals |
|||
|
|||
import logging |
|||
logger = logging.getLogger(__name__) |
|||
|
|||
try: |
|||
from PIL import Image, ImageOps |
|||
enabled = True |
|||
except ImportError: |
|||
logging.warning("Unable to load PIL, disabling thumbnailer") |
|||
enabled = False |
|||
|
|||
DEFAULT_IMAGE_DIR = "pictures" |
|||
DEFAULT_THUMBNAIL_DIR = "thumbnails" |
|||
DEFAULT_THUMBNAIL_SIZES = { |
|||
'thumbnail_square': '150', |
|||
'thumbnail_wide': '150x?', |
|||
'thumbnail_tall': '?x150', |
|||
} |
|||
DEFAULT_TEMPLATE = """<a href="{url}" rel="shadowbox" title="{filename}"><img src="{thumbnail}" alt="{filename}"></a>""" |
|||
DEFAULT_GALLERY_THUMB = "thumbnail_square" |
|||
|
|||
class _resizer(object): |
|||
""" Resizes based on a text specification, see readme """ |
|||
|
|||
REGEX = re.compile(r'(\d+|\?)x(\d+|\?)') |
|||
|
|||
def __init__(self, name, spec, root): |
|||
self._name = name |
|||
self._spec = spec |
|||
# The location of input images from _image_path. |
|||
self._root = root |
|||
|
|||
def _null_resize(self, w, h, image): |
|||
return image |
|||
|
|||
def _exact_resize(self, w, h, image): |
|||
retval = ImageOps.fit(image, (w,h), Image.BICUBIC) |
|||
return retval |
|||
|
|||
def _aspect_resize(self, w, h, image): |
|||
retval = image.copy() |
|||
retval.thumbnail((w, h), Image.ANTIALIAS) |
|||
|
|||
return retval |
|||
|
|||
def resize(self, image): |
|||
resizer = self._null_resize |
|||
|
|||
# Square resize and crop |
|||
if 'x' not in self._spec: |
|||
resizer = self._exact_resize |
|||
targetw = int(self._spec) |
|||
targeth = targetw |
|||
else: |
|||
matches = self.REGEX.search(self._spec) |
|||
tmpw = matches.group(1) |
|||
tmph = matches.group(2) |
|||
|
|||
# Full Size |
|||
if tmpw == '?' and tmph == '?': |
|||
targetw = image.size[0] |
|||
targeth = image.size[1] |
|||
resizer = self._null_resize |
|||
|
|||
# Set Height Size |
|||
if tmpw == '?': |
|||
targetw = image.size[0] |
|||
targeth = int(tmph) |
|||
resizer = self._aspect_resize |
|||
|
|||
# Set Width Size |
|||
elif tmph == '?': |
|||
targetw = int(tmpw) |
|||
targeth = image.size[1] |
|||
resizer = self._aspect_resize |
|||
|
|||
# Scale and Crop |
|||
else: |
|||
targetw = int(tmpw) |
|||
targeth = int(tmph) |
|||
resizer = self._exact_resize |
|||
|
|||
logging.debug("Using resizer {0}".format(resizer.__name__)) |
|||
return resizer(targetw, targeth, image) |
|||
|
|||
def get_thumbnail_name(self, in_path): |
|||
# Find the partial path + filename beyond the input image directory. |
|||
prefix = path.commonprefix([in_path, self._root]) |
|||
new_filename = in_path[len(prefix) + 1:] |
|||
|
|||
# Generate the new filename. |
|||
(basename, ext) = path.splitext(new_filename) |
|||
return "{0}_{1}{2}".format(basename, self._name, ext) |
|||
|
|||
def resize_file_to(self, in_path, out_path, keep_filename=False): |
|||
""" Given a filename, resize and save the image per the specification into out_path |
|||
|
|||
:param in_path: path to image file to save. Must be supported by PIL |
|||
:param out_path: path to the directory root for the outputted thumbnails to be stored |
|||
:return: None |
|||
""" |
|||
|
|||
if keep_filename: |
|||
filename = path.join(out_path, path.basename(in_path)) |
|||
else: |
|||
filename = path.join(out_path, self.get_thumbnail_name(in_path)) |
|||
out_path = path.dirname(filename) |
|||
if not path.exists(out_path): |
|||
os.makedirs(out_path) |
|||
if not path.exists(filename): |
|||
try: |
|||
image = Image.open(in_path) |
|||
thumbnail = self.resize(image) |
|||
logger.debug('test') |
|||
thumbnail.save(filename) |
|||
logger.info("Generated Thumbnail {0}".format(path.basename(filename))) |
|||
except IOError: |
|||
logger.info("Generating Thumbnail for {0} skipped".format(path.basename(filename))) |
|||
|
|||
|
|||
def resize_thumbnails(pelican): |
|||
""" Resize a directory tree full of images into thumbnails |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
global enabled |
|||
if not enabled: |
|||
return |
|||
|
|||
in_path = _image_path(pelican) |
|||
|
|||
include_regex = pelican.settings.get('THUMBNAIL_INCLUDE_REGEX') |
|||
if include_regex: |
|||
pattern = re.compile(include_regex) |
|||
is_included = lambda name: pattern.match(name) |
|||
else: |
|||
is_included = lambda name: not name.startswith('.') |
|||
|
|||
sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES) |
|||
resizers = dict((k, _resizer(k, v, in_path)) for k,v in sizes.items()) |
|||
logger.debug("Thumbnailer Started") |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if is_included(filename): |
|||
for name, resizer in resizers.items(): |
|||
in_filename = path.join(dirpath, filename) |
|||
out_path = get_out_path(pelican, in_path, in_filename, name) |
|||
resizer.resize_file_to( |
|||
in_filename, |
|||
out_path, pelican.settings.get('THUMBNAIL_KEEP_NAME')) |
|||
|
|||
|
|||
def get_out_path(pelican, in_path, in_filename, name): |
|||
base_out_path = path.join(pelican.settings['OUTPUT_PATH'], |
|||
pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR)) |
|||
logger.debug("Processing thumbnail {0}=>{1}".format(in_filename, name)) |
|||
if pelican.settings.get('THUMBNAIL_KEEP_NAME', False): |
|||
if pelican.settings.get('THUMBNAIL_KEEP_TREE', False): |
|||
return path.join(base_out_path, name, path.dirname(path.relpath(in_filename, in_path))) |
|||
else: |
|||
return path.join(base_out_path, name) |
|||
else: |
|||
return base_out_path |
|||
|
|||
|
|||
def _image_path(pelican): |
|||
return path.join(pelican.settings['PATH'], |
|||
pelican.settings.get("IMAGE_PATH", DEFAULT_IMAGE_DIR)).rstrip('/') |
|||
|
|||
|
|||
def expand_gallery(generator, metadata): |
|||
""" Expand a gallery tag to include all of the files in a specific directory under IMAGE_PATH |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
if "gallery" not in metadata or metadata['gallery'] is None: |
|||
return # If no gallery specified, we do nothing |
|||
|
|||
lines = [ ] |
|||
base_path = _image_path(generator) |
|||
in_path = path.join(base_path, metadata['gallery']) |
|||
template = generator.settings.get('GALLERY_TEMPLATE', DEFAULT_TEMPLATE) |
|||
thumbnail_name = generator.settings.get("GALLERY_THUMBNAIL", DEFAULT_GALLERY_THUMB) |
|||
thumbnail_prefix = generator.settings.get("") |
|||
resizer = _resizer(thumbnail_name, '?x?', base_path) |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if not filename.startswith('.'): |
|||
url = path.join(dirpath, filename).replace(base_path, "")[1:] |
|||
url = path.join('/static', generator.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR), url).replace('\\', '/') |
|||
logger.debug("GALLERY: {0}".format(url)) |
|||
thumbnail = resizer.get_thumbnail_name(filename) |
|||
thumbnail = path.join('/', generator.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR), thumbnail).replace('\\', '/') |
|||
lines.append(template.format( |
|||
filename=filename, |
|||
url=url, |
|||
thumbnail=thumbnail, |
|||
)) |
|||
metadata['gallery_content'] = "\n".join(lines) |
|||
|
|||
|
|||
def register(): |
|||
signals.finalized.connect(resize_thumbnails) |
|||
signals.article_generator_context.connect(expand_gallery) |
@ -0,0 +1,25 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- # |
|||
from __future__ import unicode_literals |
|||
|
|||
# This file is only used if you use `make publish` or |
|||
# explicitly specify it as your config file. |
|||
|
|||
import os |
|||
import sys |
|||
sys.path.append(os.curdir) |
|||
from pelicanconf import * |
|||
|
|||
# If your site is available via HTTPS, make sure SITEURL begins with https:// |
|||
SITEURL = 'https://multilaterale.group' |
|||
RELATIVE_URLS = False |
|||
|
|||
FEED_ALL_ATOM = 'feeds/all.atom.xml' |
|||
CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' |
|||
|
|||
DELETE_OUTPUT_DIRECTORY = True |
|||
|
|||
# Following items are often useful when publishing |
|||
|
|||
#DISQUS_SITENAME = "" |
|||
#GOOGLE_ANALYTICS = "" |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 8.9 KiB |
@ -0,0 +1,176 @@ |
|||
html { |
|||
background-color: rgb(240,240,240); |
|||
background-image:url('/theme/multilaterale_logo_opacity.svg'); |
|||
background-size:200vw; |
|||
background-position: center top; |
|||
background-repeat: no-repeat; |
|||
} |
|||
|
|||
body { |
|||
font-size:22px; |
|||
font-family: 'Kotta One', serif; |
|||
max-width:50%; |
|||
margin:0 auto; |
|||
} |
|||
|
|||
a { |
|||
color:deeppink; |
|||
text-decoration-style: wavy; |
|||
} |
|||
|
|||
h1,h2,h3{ |
|||
text-transform: uppercase; |
|||
/*font-family: 'Amatic SC', cursive;*/ |
|||
} |
|||
h3{ |
|||
margin:0; |
|||
margin-top:2em; |
|||
} |
|||
|
|||
h3 + p { |
|||
margin:0; |
|||
} |
|||
|
|||
p { |
|||
margin-bottom:0.2em; |
|||
} |
|||
|
|||
#banner{ |
|||
margin-top:2em; |
|||
margin-bottom:0em; |
|||
} |
|||
|
|||
#banner_img{ |
|||
width:100%; |
|||
} |
|||
|
|||
#menu ul li{ |
|||
display:inline; |
|||
} |
|||
|
|||
#menu ul { |
|||
padding-left:0px; |
|||
font-size: 2em; |
|||
text-align: center; |
|||
margin-top:0px; |
|||
margin-bottom:1.5em; |
|||
} |
|||
|
|||
#menu a { |
|||
color:black; |
|||
text-decoration-style: none; |
|||
} |
|||
|
|||
#menu a:hover { |
|||
color:black; |
|||
text-decoration-style: wavy; |
|||
text-decoration-color: deeppink; |
|||
} |
|||
|
|||
#menu .active a{ |
|||
|
|||
text-decoration-style: wavy; |
|||
text-decoration-color: deeppink; |
|||
} |
|||
|
|||
.featured_image{ |
|||
display:inline-block; |
|||
max-width: 40%; |
|||
} |
|||
|
|||
.featured_image img { |
|||
max-width: 100%; |
|||
} |
|||
|
|||
.summary{ |
|||
display:inline-block; |
|||
max-width: 50%; |
|||
vertical-align: top; |
|||
margin-left: 1em; |
|||
} |
|||
|
|||
.summary h2 { |
|||
text-transform: uppercase; |
|||
margin-bottom: 0; |
|||
/*border-bottom: 6px solid black;*/ |
|||
} |
|||
|
|||
.entry-content p img { |
|||
max-width: 100%; |
|||
} |
|||
|
|||
#data-left, #data-right{ |
|||
max-width: 49%; |
|||
display:inline-block; |
|||
vertical-align: top; |
|||
} |
|||
|
|||
#data-left img{ |
|||
height:100%; |
|||
} |
|||
|
|||
#data h3 { |
|||
margin:0.3em; |
|||
} |
|||
|
|||
#data ul { |
|||
margin:0.5em; |
|||
} |
|||
|
|||
@media only screen and (max-width: 600px) { |
|||
body { |
|||
font-size:24px; |
|||
max-width: 92% |
|||
} |
|||
#menu ul { |
|||
font-size:1.5em; |
|||
} |
|||
#banner{ |
|||
margin-top:1em; |
|||
} |
|||
|
|||
#data-left, #data-right{ |
|||
max-width: 100%; |
|||
display:inline-block; |
|||
} |
|||
|
|||
} |
|||
|
|||
@media only screen and (max-width: 300px) { |
|||
body { |
|||
font-size:18px; |
|||
max-width: 92% |
|||
} |
|||
#menu ul { |
|||
font-size:1.2em; |
|||
} |
|||
#banner{ |
|||
margin-top:1em; |
|||
} |
|||
} |
|||
|
|||
@media only screen and (orientation: portrait) { |
|||
body { |
|||
font-size:48px; |
|||
max-width: 92% |
|||
} |
|||
|
|||
#menu ul { |
|||
font-size:1.5em; |
|||
} |
|||
#banner{ |
|||
margin-top:1em; |
|||
} |
|||
.featured_image{ |
|||
max-width: 100%; |
|||
} |
|||
|
|||
.featured_image img { |
|||
max-width: 100%; |
|||
} |
|||
|
|||
.summary{ |
|||
max-width: 100%; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,14 @@ |
|||
{% extends "base.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - Archives{% endblock %} |
|||
|
|||
{% block content %} |
|||
<h1>Archives for {{ SITENAME }}</h1> |
|||
|
|||
<dl> |
|||
{% for article in dates %} |
|||
<dt>{{ article.locale_date }}</dt> |
|||
<dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd> |
|||
{% endfor %} |
|||
</dl> |
|||
{% endblock %} |
@ -0,0 +1,30 @@ |
|||
{% extends "base.html" %} |
|||
{% block html_lang %}{{ article.lang }}{% endblock %} |
|||
|
|||
{% block title %}{{ SITENAME }} - {{ article.title }}{% endblock %} |
|||
|
|||
{% block head %} |
|||
{{ super() }} |
|||
|
|||
{% import 'translations.html' as translations with context %} |
|||
{% if translations.entry_hreflang(article) %} |
|||
{{ translations.entry_hreflang(article) }} |
|||
{% endif %} |
|||
{% if article.description %} |
|||
<meta name="description" content="{{article.description}}" /> |
|||
{% endif %} |
|||
{% for tag in article.tags %} |
|||
<meta name="tags" content="{{tag}}" /> |
|||
{% endfor %} |
|||
|
|||
{% endblock %} |
|||
|
|||
|
|||
{% block content %} |
|||
<section id="content" class="body"> |
|||
<div class="entry-content"> |
|||
<h3>{{article.title}}</h3> |
|||
{{ article.content }} |
|||
</div><!-- /.entry-content --> |
|||
</section> |
|||
{% endblock %} |
@ -0,0 +1,8 @@ |
|||
{% extends "index.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - Articles by {{ author }}{% endblock %} |
|||
|
|||
{% block content_title %} |
|||
<h2>Articles by {{ author }}</h2> |
|||
{% endblock %} |
|||
|
@ -0,0 +1,12 @@ |
|||
{% extends "base.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - Authors{% endblock %} |
|||
|
|||
{% block content %} |
|||
<h1>Authors on {{ SITENAME }}</h1> |
|||
<ul> |
|||
{% for author, articles in authors|sort %} |
|||
<li><a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a> ({{ articles|count }})</li> |
|||
{% endfor %} |
|||
</ul> |
|||
{% endblock %} |
@ -0,0 +1,48 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="{% block html_lang %}{{ DEFAULT_LANG }}{% endblock html_lang %}"> |
|||
<head> |
|||
{% block head %} |
|||
<title>{% block title %}{{ SITENAME }}{% endblock title %}</title> |
|||
<meta charset="utf-8" /> |
|||
<link rel="stylesheet" type="text/css" href="{{SITEURL}}/theme/style.css"> |
|||
|
|||
<link href="https://fonts.googleapis.com/css?family=Kotta+One&display=swap" rel="stylesheet"> |
|||
|
|||
{% block rss %} |
|||
{% endblock rss %} |
|||
{% endblock head %} |
|||
</head> |
|||
|
|||
<body id="index" class="home"> |
|||
{% block menu %} |
|||
<header id="banner" class="body"> |
|||
<a href="{{ SITEURL }}/"><img id="banner_img"src="/theme/multilaterale_text.svg"></a> |
|||
</header><!-- /#banner --> |
|||
<nav id="menu"><ul> |
|||
{% for title, link in MENUITEMS %} |
|||
<li><a href="{{ link }}">{{ title }}</a></li> |
|||
{% endfor %} |
|||
{% if DISPLAY_PAGES_ON_MENU %} |
|||
{% for p in pages %} |
|||
<li{% if p == page %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ p.url }}">{{ p.title }}</a> - </li> |
|||
{% endfor %} |
|||
{% endif %} |
|||
{% if DISPLAY_CATEGORIES_ON_MENU %} |
|||
{% for cat, null in categories %} |
|||
{% if loop.last %} |
|||
<li{% if cat == category %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a> </li> |
|||
{% else %} |
|||
<li{% if cat == category %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a> - </li> |
|||
{%endif%} |
|||
{% endfor %} |
|||
{% endif %} |
|||
</ul></nav><!-- /#menu --> |
|||
{% endblock menu %} |
|||
{% block content %} |
|||
{% endblock %} |
|||
<footer id="contentinfo" class="body"> |
|||
<address id="about" class="vcard body"> |
|||
</address><!-- /#about --> |
|||
</footer><!-- /#contentinfo --> |
|||
</body> |
|||
</html> |
@ -0,0 +1,12 @@ |
|||
{% extends "base.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - Categories{% endblock %} |
|||
|
|||
{% block content %} |
|||
<h1>Categories on {{ SITENAME }}</h1> |
|||
<ul> |
|||
{% for category, articles in categories|sort %} |
|||
<li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a> ({{ articles|count }})</li> |
|||
{% endfor %} |
|||
</ul> |
|||
{% endblock %} |
@ -0,0 +1,35 @@ |
|||
{% extends "index.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - {{ category }} category{% endblock %} |
|||
|
|||
{% block content_title %} |
|||
<h2>Articles in the {{ category }} category</h2> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
<section id="content"> |
|||
|
|||
<div id="post-list"> |
|||
{% for article in articles_page.object_list %} |
|||
|
|||
<article class="hentry"> |
|||
<div class="entry-content"> |
|||
{% if article.category != 'Brews' %} |
|||
<header> <h2 class="entry-title log"><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }} | {{ article.date | strftime('%d %B %Y')}}</a></h2> </header> |
|||
{{article.summary}} |
|||
{% else %} |
|||
<header> <h2 class="entry-title log"><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2> </header> |
|||
<a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}"><img src="{{SITURL}}/{{article.featured_image}}"></a> |
|||
{{article.summary}} |
|||
{% endif %} |
|||
|
|||
</div> |
|||
</article> |
|||
{% endfor %} |
|||
</div> |
|||
<!-- /#posts-list --> |
|||
{% if articles_page.has_other_pages() %} |
|||
{% include 'pagination.html' %} |
|||
{% endif %} |
|||
</section><!-- /#content --> |
|||
{% endblock content %} |
@ -0,0 +1,14 @@ |
|||
{% if GOSQUARED_SITENAME %} |
|||
<script type="text/javascript"> |
|||
var GoSquared={}; |
|||
GoSquared.acct = "{{ GOSQUARED_SITENAME }}"; |
|||
(function(w){ |
|||
function gs(){ |
|||
w._gstc_lt=+(new Date); var d=document; |
|||
var g = d.createElement("script"); g.type = "text/javascript"; g.async = true; g.src = "https://d1l6p2sc9645hc.cloudfront.net/tracker.js"; |
|||
var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(g, s); |
|||
} |
|||
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs); |
|||
})(window); |
|||
</script> |
|||
{% endif %} |
@ -0,0 +1,45 @@ |
|||
{% extends "base.html" %} |
|||
{% block content %} |
|||
<section id="content"> |
|||
{% block content_title %} |
|||
|
|||
{% endblock %} |
|||
|
|||
<div id="post-list"> |
|||
{% for article in articles_page.object_list %} |
|||
{% if article.category != 'News' %} |
|||
<article class="hentry"> |
|||
<div class="entry-content"> |
|||
<div class="featured_image"> |
|||
{% if article.featured_image %} |
|||
<img src="{{SITEURL}}/{{ article.featured_image }}"> |
|||
{% endif %}</div> |
|||
<div class="summary"> |
|||
<header> <h2 class="entry-title"><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2> </header> |
|||
{{article.summary}}<a href="{{ SITEURL }}/{{ article.url }}">[continued]</a> |
|||
</div> |
|||
</div><!-- /.entry-content --> |
|||
</article> |
|||
{% endif %} |
|||
{% endfor %} |
|||
<h2>UPDATES</h2> |
|||
{% for article in articles_page.object_list %} |
|||
{% if article.category != 'Brews' %} |
|||
<article class="hentry"> |
|||
<div class="entry-content"> |
|||
<div class="summary"> |
|||
<header> <h2 class="entry-title"><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2> </header> |
|||
{{article.date | strftime('%d %B %Y')}} |
|||
{{article.summary}} |
|||
</div> |
|||
</div><!-- /.entry-content --> |
|||
</article> |
|||
{% endif %} |
|||
{% endfor %} |
|||
</div> |
|||
<!-- /#posts-list --> |
|||
{% if articles_page.has_other_pages() %} |
|||
{% include 'pagination.html' %} |
|||
{% endif %} |
|||
</section><!-- /#content --> |
|||
{% endblock content %} |
@ -0,0 +1,26 @@ |
|||
{% extends "base.html" %} |
|||
{% block html_lang %}{{ page.lang }}{% endblock %} |
|||
|
|||
{% block title %}{{ SITENAME }} - {{ page.title }}{%endblock%} |
|||
|
|||
{% block head %} |
|||
{{ super() }} |
|||
|
|||
{% import 'translations.html' as translations with context %} |
|||
{% if translations.entry_hreflang(page) %} |
|||
{{ translations.entry_hreflang(page) }} |
|||
{% endif %} |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
{% import 'translations.html' as translations with context %} |
|||
{{ translations.translations_for(page) }} |
|||
|
|||
{{ page.content }} |
|||
|
|||
{% if page.modified %} |
|||
<p> |
|||
Last updated: {{ page.locale_modified }} |
|||
</p> |
|||
{% endif %} |
|||
{% endblock %} |
@ -0,0 +1,11 @@ |
|||
{% if DEFAULT_PAGINATION %} |
|||
<p class="paginator"> |
|||
{% if articles_page.has_previous() %} |
|||
<a href="{{ SITEURL }}/{{ articles_previous_page.url }}">«</a> |
|||
{% endif %} |
|||
Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} |
|||
{% if articles_page.has_next() %} |
|||
<a href="{{ SITEURL }}/{{ articles_next_page.url }}">»</a> |
|||
{% endif %} |
|||
</p> |
|||
{% endif %} |
@ -0,0 +1,14 @@ |
|||
{% extends "base.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - {{ period | reverse | join(' ') }} archives{% endblock %} |
|||
|
|||
{% block content %} |
|||
<h1>Archives for {{ period | reverse | join(' ') }}</h1> |
|||
|
|||
<dl> |
|||
{% for article in dates %} |
|||
<dt>{{ article.locale_date }}</dt> |
|||
<dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd> |
|||
{% endfor %} |
|||
</dl> |
|||
{% endblock %} |
@ -0,0 +1,27 @@ |
|||
{% extends "base.html" %} |
|||
{%block rss%} |
|||
{% if FEED_ALL_ATOM %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Full Atom Feed" /> |
|||
{% endif %} |
|||
{% if FEED_ALL_RSS %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_RSS_URL %}{{ FEED_ALL_RSS_URL }}{% else %}{{ FEED_ALL_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Full RSS Feed" /> |
|||
{% endif %} |
|||
{% if FEED_ATOM %} |
|||
<link href="{{ FEED_DOMAIN }}/{%if FEED_ATOM_URL %}{{ FEED_ATOM_URL }}{% else %}{{ FEED_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" /> |
|||
{% endif %} |
|||
{% if FEED_RSS %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if FEED_RSS_URL %}{{ FEED_RSS_URL }}{% else %}{{ FEED_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" /> |
|||
{% endif %} |
|||
{% if CATEGORY_FEED_ATOM and category %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_ATOM_URL %}{{ CATEGORY_FEED_ATOM_URL|format(category.slug) }}{% else %}{{ CATEGORY_FEED_ATOM|format(category.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Categories Atom Feed" /> |
|||
{% endif %} |
|||
{% if CATEGORY_FEED_RSS and category %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_RSS_URL %}{{ CATEGORY_FEED_RSS_URL|format(category.slug) }}{% else %}{{ CATEGORY_FEED_RSS|format(category.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Categories RSS Feed" /> |
|||
{% endif %} |
|||
{% if TAG_FEED_ATOM and tag %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_ATOM_URL %}{{ TAG_FEED_ATOM_URL|format(tag.slug) }}{% else %}{{ TAG_FEED_ATOM|format(tag.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Tags Atom Feed" /> |
|||
{% endif %} |
|||
{% if TAG_FEED_RSS and tag %} |
|||
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_RSS_URL %}{{ TAG_FEED_RSS_URL|format(tag.slug) }}{% else %}{{ TAG_FEED_RSS|format(tag.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} Tags RSS Feed" /> |
|||
{% endif %} |
|||
{%endblock rss%} |
@ -0,0 +1,7 @@ |
|||
{% extends "index.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - {{ tag }} tag{% endblock %} |
|||
|
|||
{% block content_title %} |
|||
<h2>Articles tagged with {{ tag }}</h2> |
|||
{% endblock %} |
@ -0,0 +1,12 @@ |
|||
{% extends "base.html" %} |
|||
|
|||
{% block title %}{{ SITENAME }} - Tags{% endblock %} |
|||
|
|||
{% block content %} |
|||
<h1>Tags for {{ SITENAME }}</h1> |
|||
<ul> |
|||
{% for tag, articles in tags|sort %} |
|||
<li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a> ({{ articles|count }})</li> |
|||
{% endfor %} |
|||
</ul> |
|||
{% endblock %} |
@ -0,0 +1,16 @@ |
|||
{% macro translations_for(article) %} |
|||
{% if article.translations %} |
|||
Translations: |
|||
{% for translation in article.translations %} |
|||
<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}">{{ translation.lang }}</a> |
|||
{% endfor %} |
|||
{% endif %} |
|||
{% endmacro %} |
|||
|
|||
{% macro entry_hreflang(entry) %} |
|||
{% if entry.translations %} |
|||
{% for translation in entry.translations %} |
|||
<link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}"> |
|||
{% endfor %} |
|||
{% endif %} |
|||
{% endmacro %} |