kopia lustrzana https://github.com/lowtechmag/solar-plugins
update pelican plugins as they are now used on solar.ltm
rodzic
4bf4ca7167
commit
3d0a753f06
|
@ -1,8 +1,24 @@
|
|||
"""
|
||||
Addressable Paragraphs
|
||||
------------------------
|
||||
In converting from MD to html images are wrapped in <p> objects.
|
||||
In converting from .md to .html, images are wrapped in <p> tags.
|
||||
This plugin gives those paragraphs the 'img' class for styling enhancements.
|
||||
In case there is any description immediately following that image, it is wrapped in another paragraph with the 'caption' class.
|
||||
|
||||
Copyright (C) 2018 Roel Roscam Abbing
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
@ -18,11 +34,12 @@ def content_object_init(instance):
|
|||
for p in soup(['p', 'object']):
|
||||
if p.findChild('img'):
|
||||
p.attrs['class'] = 'img'
|
||||
caption = soup.new_tag('span',**{'class':'caption'})
|
||||
for i in reversed(p.contents):
|
||||
if i.name != 'img': #if it is not an <img> tag
|
||||
caption.insert(0,i.extract())
|
||||
p.append(caption)
|
||||
caption = soup.new_tag('p',**{'class':'caption'})
|
||||
if len(p.contents) > 1: #if we have more than just the <img> tag
|
||||
for i in reversed(p.contents):
|
||||
if i.name != 'img': #if it is not an <img> tag
|
||||
caption.insert(0,i.extract())
|
||||
p.insert_after(caption)
|
||||
|
||||
instance._content = soup.decode()
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
cssmin
|
||||
webassets
|
|
@ -18,11 +18,11 @@ CUR_DIR = os.path.dirname(__file__)
|
|||
THEME_DIR = os.path.join(CUR_DIR, 'test_data')
|
||||
CSS_REF = open(os.path.join(THEME_DIR, 'static', 'css',
|
||||
'style.min.css')).read()
|
||||
CSS_HASH = hashlib.md5(CSS_REF).hexdigest()[0:8]
|
||||
CSS_HASH = hashlib.md5(CSS_REF.encode()).hexdigest()[0:8]
|
||||
|
||||
|
||||
@unittest.skipUnless(module_exists('webassets'), "webassets isn't installed")
|
||||
@skipIfNoExecutable(['scss', '-v'])
|
||||
@skipIfNoExecutable(['sass', '-v'])
|
||||
@skipIfNoExecutable(['cssmin', '--version'])
|
||||
class TestWebAssets(unittest.TestCase):
|
||||
"""Base class for testing webassets."""
|
||||
|
|
|
@ -53,7 +53,7 @@ def _image_path(pelican):
|
|||
|
||||
def _out_path(pelican):
|
||||
return os.path.join(pelican.settings['OUTPUT_PATH'],
|
||||
pelican.settings.get('DITHER_DIR', DEFAULT_DITHER_DIR))
|
||||
pelican.settings.get('DITHER_DIR', DEFAULT_DITHER_DIR)).rstrip('/')
|
||||
|
||||
def dither(pelican):
|
||||
global enabled
|
||||
|
@ -64,34 +64,35 @@ def dither(pelican):
|
|||
out_path = _out_path(pelican)
|
||||
|
||||
transparency = pelican.settings.get("TRANSPARENCY",DEFAULT_TRANSPARENCY)
|
||||
|
||||
STABLE_SITEURL = pelican.settings.get("STABLE_SITEURL")
|
||||
if not os.path.exists(out_path):
|
||||
os.mkdir(out_path)
|
||||
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
file_, ext = os.path.splitext(filename)
|
||||
fn= os.path.join(dirpath,filename)
|
||||
of = os.path.join(out_path, filename.replace(ext,'.png'))
|
||||
if not os.path.exists(of) and imghdr.what(fn):
|
||||
logging.debug("dither plugin: dithering {}".format(fn))
|
||||
if pelican.settings.get("SITEURL") == STABLE_SITEURL:
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
file_, ext = os.path.splitext(filename)
|
||||
fn= os.path.join(dirpath,filename)
|
||||
of = os.path.join(out_path, filename.replace(ext,'.png'))
|
||||
if not os.path.exists(of) and imghdr.what(fn):
|
||||
logging.debug("Dither plugin: dithering {}".format(fn))
|
||||
|
||||
img= Image.open(fn).convert('RGB')
|
||||
img= Image.open(fn).convert('RGB')
|
||||
|
||||
resize = pelican.settings.get('RESIZE', DEFAULT_RESIZE_OUTPUT)
|
||||
resize = pelican.settings.get('RESIZE', DEFAULT_RESIZE_OUTPUT)
|
||||
|
||||
if resize:
|
||||
image_size = pelican.settings.get('SIZE', DEFAULT_MAX_SIZE)
|
||||
img.thumbnail(image_size, Image.LANCZOS)
|
||||
|
||||
palette = hitherdither.palette.Palette(pelican.settings.get('DITHER_PALETTE', DEFAULT_DITHER_PALETTE))
|
||||
|
||||
threshold = pelican.settings.get('THRESHOLD', DEFAULT_THRESHOLD)
|
||||
|
||||
img_dithered = hitherdither.ordered.bayer.bayer_dithering(img, palette, threshold, order=8) #see hither dither documentation for different dithering algos
|
||||
if resize:
|
||||
image_size = pelican.settings.get('SIZE', DEFAULT_MAX_SIZE)
|
||||
img.thumbnail(image_size, Image.LANCZOS)
|
||||
|
||||
palette = hitherdither.palette.Palette(pelican.settings.get('DITHER_PALETTE', DEFAULT_DITHER_PALETTE))
|
||||
|
||||
threshold = pelican.settings.get('THRESHOLD', DEFAULT_THRESHOLD)
|
||||
|
||||
img_dithered = hitherdither.ordered.bayer.bayer_dithering(img, palette, threshold, order=8) #see hither dither documentation for different dithering algos
|
||||
|
||||
img_dithered.save(of, optimize=True)
|
||||
#logging.debug(calculate_savings(in_path,out_path))
|
||||
img_dithered.save(of, optimize=True)
|
||||
#logging.debug(calculate_savings(in_path,out_path))
|
||||
|
||||
def parse_for_images(instance):
|
||||
#based on better_figures_and_images plugin by @dflock, @phrawzty,@if1live,@jar1karp,@dhalperi,@aqw,@adworacz
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Neighbor Articles Plugin for Pelican
|
||||
====================================
|
||||
|
||||
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
|
||||
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
|
||||
variables to the article's context.
|
||||
|
||||
Also adds ``next_article_in_category`` and ``prev_article_in_category``.
|
||||
|
@ -27,7 +27,7 @@ Usage
|
|||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
<ul>
|
||||
{% if article.prev_article_in_category %}
|
||||
<li>
|
||||
|
@ -43,21 +43,21 @@ Usage
|
|||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
Usage with the Subcategory plugin
|
||||
---------------------------------
|
||||
|
||||
If you want to get the neigbors within a subcategory it's a little different.
|
||||
Since an article can belong to more than one subcategory, subcategories are
|
||||
stored in a list. If you have an article with subcategories like
|
||||
stored in a list. If you have an article with subcategories like
|
||||
|
||||
``Category/Foo/Bar``
|
||||
|
||||
it will belong to both subcategory Foo, and Foo/Bar. Subcategory neighbors are
|
||||
added to an article as ``next_article_in_subcategory#`` and
|
||||
added to an article as ``next_article_in_subcategory#`` and
|
||||
``prev_article_in_subcategory#`` where ``#`` is the level of subcategory. So using
|
||||
the example from above, subcategory1 will be Foo, and subcategory2 Foo/Bar.
|
||||
the example from above, subcategory1 will be Foo, and subcategory2 Foo/Bar.
|
||||
Therefor the usage with subcategories is:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
@ -77,7 +77,7 @@ Therefor the usage with subcategories is:
|
|||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
<ul>
|
||||
{% if article.prev_article_in_subcategory2 %}
|
||||
<li>
|
||||
|
@ -93,5 +93,5 @@ Therefor the usage with subcategories is:
|
|||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -3,19 +3,24 @@
|
|||
Neighbor Articles Plugin for Pelican
|
||||
====================================
|
||||
|
||||
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
|
||||
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
|
||||
variables to the article's context
|
||||
"""
|
||||
from pelican import signals
|
||||
|
||||
|
||||
def iter3(seq):
|
||||
it = iter(seq)
|
||||
nxt = None
|
||||
cur = next(it)
|
||||
for prv in it:
|
||||
yield nxt, cur, prv
|
||||
"""Generate one triplet per element in 'seq' following PEP-479."""
|
||||
nxt, cur = None, None
|
||||
for prv in seq:
|
||||
if cur:
|
||||
yield nxt, cur, prv
|
||||
nxt, cur = cur, prv
|
||||
yield nxt, cur, None
|
||||
# Don't yield anything if empty seq
|
||||
if cur:
|
||||
# Yield last element in seq (also if len(seq) == 1)
|
||||
yield nxt, cur, None
|
||||
|
||||
|
||||
def get_translation(article, prefered_language):
|
||||
if not article:
|
||||
|
@ -25,6 +30,7 @@ def get_translation(article, prefered_language):
|
|||
return translation
|
||||
return article
|
||||
|
||||
|
||||
def set_neighbors(articles, next_name, prev_name):
|
||||
for nxt, cur, prv in iter3(articles):
|
||||
exec("cur.{} = nxt".format(next_name))
|
||||
|
@ -32,27 +38,29 @@ def set_neighbors(articles, next_name, prev_name):
|
|||
|
||||
for translation in cur.translations:
|
||||
exec(
|
||||
"translation.{} = get_translation(nxt, translation.lang)".format(
|
||||
next_name))
|
||||
"translation.{} = get_translation(nxt, translation.lang)"
|
||||
.format(next_name))
|
||||
exec(
|
||||
"translation.{} = get_translation(prv, translation.lang)".format(
|
||||
prev_name))
|
||||
|
||||
"translation.{} = get_translation(prv, translation.lang)"
|
||||
.format(prev_name))
|
||||
|
||||
|
||||
def neighbors(generator):
|
||||
set_neighbors(generator.articles, 'next_article', 'prev_article')
|
||||
|
||||
|
||||
for category, articles in generator.categories:
|
||||
articles.sort(key=(lambda x: x.date), reverse=(True))
|
||||
articles.sort(key=lambda x: x.date, reverse=True)
|
||||
set_neighbors(
|
||||
articles, 'next_article_in_category', 'prev_article_in_category')
|
||||
|
||||
if hasattr(generator, 'subcategories'):
|
||||
for subcategory, articles in generator.subcategories:
|
||||
articles.sort(key=(lambda x: x.date), reverse=(True))
|
||||
articles.sort(key=lambda x: x.date, reverse=True)
|
||||
index = subcategory.name.count('/')
|
||||
next_name = 'next_article_in_subcategory{}'.format(index)
|
||||
prev_name = 'prev_article_in_subcategory{}'.format(index)
|
||||
set_neighbors(articles, next_name, prev_name)
|
||||
|
||||
|
||||
def register():
|
||||
signals.article_generator_finalized.connect(neighbors)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
Title: Test md File
|
||||
Category: test
|
||||
Tags: foo, bar, foobar
|
||||
Date: 2010-12-02 10:14
|
||||
Modified: 2010-12-02 10:20
|
||||
Summary: I have a lot to test
|
||||
|
||||
Test Markdown File Header
|
||||
=========================
|
||||
|
||||
Used for pelican test
|
||||
---------------------
|
||||
|
||||
The quick brown fox jumped over the lazy dog's back.
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from os.path import dirname, join
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from pelican.generators import ArticlesGenerator
|
||||
from pelican.tests.support import get_settings, unittest
|
||||
|
||||
from .neighbors import neighbors
|
||||
|
||||
|
||||
CUR_DIR = dirname(__file__)
|
||||
|
||||
|
||||
class NeighborsTest(unittest.TestCase):
|
||||
def test_neighbors_basic(self):
|
||||
with TemporaryDirectory() as tmpdirname:
|
||||
generator = _build_article_generator(join(CUR_DIR, '..', 'test_data'), tmpdirname)
|
||||
neighbors(generator)
|
||||
def test_neighbors_with_single_article(self):
|
||||
with TemporaryDirectory() as tmpdirname:
|
||||
generator = _build_article_generator(join(CUR_DIR, 'test_data'), tmpdirname)
|
||||
neighbors(generator)
|
||||
|
||||
|
||||
def _build_article_generator(content_path, output_path):
|
||||
settings = get_settings(filenames={})
|
||||
settings['PATH'] = content_path
|
||||
context = settings.copy()
|
||||
context['generated_content'] = dict()
|
||||
context['static_links'] = set()
|
||||
article_generator = ArticlesGenerator(
|
||||
context=context, settings=settings,
|
||||
path=settings['PATH'], theme=settings['THEME'], output_path=output_path)
|
||||
article_generator.generate_context()
|
||||
return article_generator
|
|
@ -0,0 +1,32 @@
|
|||
#Page Meta-Data
|
||||
|
||||
A Pelican plugin to add the total page size to each generated page of your site.
|
||||
|
||||
It calculates the weight of the HTML page including all image media and returns that in a human readable format (B, KB, MB).
|
||||
|
||||
## Caveats:
|
||||
it currently is tailored to https://solar.lowtechmagazine.com and needs work in the following areas:
|
||||
|
||||
* add options to show file name and generation time
|
||||
* properly handle subsites plugin (currently it only works for dither+subsites)
|
||||
* make sure it works with --relative-urls flag
|
||||
* handle static assets
|
||||
|
||||
|
||||
## Use:
|
||||
To enable the plugin add it to the `PLUGINS` list in `pelicanconf.py`.
|
||||
|
||||
Add a div with id `page-size` to your template and `page_metadata` will place the result there.
|
||||
|
||||
have fun!
|
||||
|
||||
|
||||
## in case we add generation time:
|
||||
|
||||
To use this plugin first import `strftime` at the top of `pelicanconf.py`:
|
||||
|
||||
`from time import strftime`
|
||||
|
||||
Then add `NOW = strftime('%c')` somewhere in that document as well. This saves the time of generation as a variable that is usable by the `page_metadata` plugin.
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
from .page_metadata import *
|
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# -*- coding: utf-8 -*- #
|
||||
|
||||
# Page Meta-Data
|
||||
# ------------------------
|
||||
# Insert meta-data about the generated file into the resulting HMTL.
|
||||
# Copyright (C) 2019 Roel Roscam Abbing
|
||||
#
|
||||
# Support your local Low-Tech Magazine:
|
||||
# https://solar.lowtechmagazine.com/donate.html
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from pelican import signals
|
||||
from bs4 import BeautifulSoup
|
||||
import os
|
||||
|
||||
|
||||
def get_printable_size(byte_size):
|
||||
"""
|
||||
Thanks Pobux!
|
||||
https://gist.github.com/Pobux/0c474672b3acd4473d459d3219675ad8
|
||||
"""
|
||||
BASE_SIZE = 1024.00
|
||||
MEASURE = ["B", "KB", "MB", "GB", "TB", "PB"]
|
||||
|
||||
def _fix_size(size, size_index):
|
||||
if not size:
|
||||
return "0"
|
||||
elif size_index == 0:
|
||||
return str(size)
|
||||
else:
|
||||
return "{:.2f}".format(size)
|
||||
|
||||
current_size = byte_size
|
||||
size_index = 0
|
||||
|
||||
while current_size >= BASE_SIZE and len(MEASURE) != size_index:
|
||||
current_size = current_size / BASE_SIZE
|
||||
size_index = size_index + 1
|
||||
|
||||
size = _fix_size(current_size, size_index)
|
||||
measure = MEASURE[size_index]
|
||||
return size + measure
|
||||
|
||||
def get_assets(soup):
|
||||
assets = []
|
||||
for a in soup.findAll('link', {'rel':['apple-touch-icon','icon','stylesheet']}):
|
||||
a = a['href'].split('?')[0]
|
||||
if a not in assets:
|
||||
assets.append(a)
|
||||
return assets
|
||||
|
||||
def get_media(html_file):
|
||||
"""
|
||||
Currently only images because I, for one, am lazy.
|
||||
"""
|
||||
html_file = open(html_file).read()
|
||||
soup = BeautifulSoup(html_file, 'html.parser')
|
||||
media = []
|
||||
|
||||
for img in soup(['img', 'object']):
|
||||
media.append(img['src'])
|
||||
|
||||
featured_images = soup.findAll('div', {'class':'featured-img'})
|
||||
for fi in featured_images:
|
||||
fi = fi['style']
|
||||
start = fi.find("url('")
|
||||
end = fi.find("');")
|
||||
url = fi[start+len("url('"):end]
|
||||
media.append(url)
|
||||
|
||||
assets = get_assets(soup)
|
||||
media = list(set(media+assets)) # duplicate media don't increase page size
|
||||
return media, soup
|
||||
|
||||
def generate_metadata(path, context):
|
||||
output_path = context['OUTPUT_PATH']
|
||||
output_file = context['output_file']
|
||||
siteurl = context['SITEURL']
|
||||
plugins = context['PLUGINS']
|
||||
subsites = False
|
||||
|
||||
if 'i18n_subsites' in plugins:
|
||||
subsites = True
|
||||
lang = context['DEFAULT_LANG']
|
||||
general_output_path = output_path.replace(lang, '')
|
||||
siteurl = context['main_siteurl']
|
||||
|
||||
media_size = 0
|
||||
# enumerate all media displayed on the page
|
||||
media, soup = get_media(path) #reuse the same soup to limit calculation
|
||||
|
||||
for m in media:
|
||||
# filter out SITEURL to prevent trouble
|
||||
# join output path to file, need to strip any leading slash for os.path
|
||||
if subsites:
|
||||
file_name = m.replace(context['main_siteurl']+'/', '')
|
||||
m = os.path.join(general_output_path, file_name.strip('/'))
|
||||
else:
|
||||
file_name = m.replace(context['SITEURL']+'/', '')
|
||||
m = os.path.join(output_path, file_name.strip('/'))
|
||||
#print(m)
|
||||
if os.path.exists(m):
|
||||
#print(m, 'exists')
|
||||
media_size = media_size + os.path.getsize(m)
|
||||
|
||||
current_file = os.path.join(output_path, output_file)
|
||||
file_size = os.path.getsize(current_file)
|
||||
|
||||
file_size = file_size + media_size
|
||||
metadata = get_printable_size(file_size)
|
||||
metadata = get_printable_size(file_size+len(metadata)) # cursed code is cursed
|
||||
|
||||
insert_metadata(path, metadata, soup)
|
||||
|
||||
def insert_metadata(output_file, metadata, soup):
|
||||
tag = soup.find('div', {'id':'page-size'})
|
||||
if tag:
|
||||
with open(output_file,'w') as f:
|
||||
tag.string = '{}'.format(metadata)
|
||||
f.write(str(soup))
|
||||
|
||||
def register():
|
||||
signals.content_written.connect(generate_metadata)
|
|
@ -1,29 +1,31 @@
|
|||
# Summary
|
||||
|
||||
This plugin extracts a representative image (i.e, featured image) from the article's summary or content if not specifed in the metadata.
|
||||
This plugin extracts a representative image (i.e, featured image) from the summary or content of an article or a page, if not specified in the metadata.
|
||||
|
||||
The plugin also removes any images from the summary after extraction to avoid duplication.
|
||||
The plugin also removes any images from the summary after extraction to avoid duplication.
|
||||
|
||||
It allows the flexibility on where and how to display the featured image of an article together with its summary in a template page. For example, the article metadata can be displayed in thumbnail format, in which there is a short summary and an image. The layout of the summary and the image can be varied for aesthetical purpose. It doesn't have to depend on article's content format.
|
||||
It allows the flexibility on where and how to display the featured image of an article together with its summary in a template page. For example, the article metadata can be displayed in thumbnail format, in which there is a short summary and an image. The layout of the summary and the image can be varied for aesthetical purpose. It doesn't have to depend on article's content format.
|
||||
|
||||
Installation
|
||||
------------
|
||||
## Installation
|
||||
|
||||
This plugin requires BeautifulSoup.
|
||||
This plugin requires `BeautifulSoup`:
|
||||
|
||||
pip install beautifulsoup4
|
||||
```bash
|
||||
pip install beautifulsoup4
|
||||
```
|
||||
|
||||
To enable, add the following to your settings.py:
|
||||
To enable, add the following to your `settings.py`:
|
||||
|
||||
PLUGIN_PATH = 'path/to/pelican-plugins'
|
||||
PLUGINS = ["representative_image"]
|
||||
```python
|
||||
PLUGIN_PATH = 'path/to/pelican-plugins'
|
||||
PLUGINS = ["representative_image"]
|
||||
```
|
||||
|
||||
`PLUGIN_PATH` can be a path relative to your settings file or an absolute path.
|
||||
|
||||
Usage
|
||||
-----
|
||||
## Usage
|
||||
|
||||
To override the default behaviour of selecting the first image in the article's summary or content, set the image property the article's metadata to the url of the image to display, e.g:
|
||||
To override the default behavior of selecting the first image in the article's summary or content, set the image property the article's metadata to the URL of the image to display, e.g:
|
||||
|
||||
```markdown
|
||||
Title: My super title
|
||||
|
@ -38,8 +40,22 @@ Image: /images/my-super-image.png
|
|||
Article content...
|
||||
```
|
||||
|
||||
### Page
|
||||
|
||||
To include a representative image in a page add the following to the template:
|
||||
|
||||
{% if article.featured_image %}
|
||||
<img src="{{ article.featured_image }}">
|
||||
{% endif %}
|
||||
```html
|
||||
{% if page.featured_image %}
|
||||
<img src="{{ page.featured_image }}">
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Article
|
||||
|
||||
To include a representative image in an article add the following to the template:
|
||||
|
||||
```html
|
||||
{% if article.featured_image %}
|
||||
<img src="{{ article.featured_image }}">
|
||||
{% endif %}
|
||||
```
|
||||
|
|
|
@ -1 +1 @@
|
|||
from .representative_image import *
|
||||
from .representative_image import * # noqa
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import six
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from pelican import signals
|
||||
from pelican.contents import Article, Page
|
||||
from pelican.generators import ArticlesGenerator
|
||||
from bs4 import BeautifulSoup
|
||||
from pelican.generators import ArticlesGenerator, PagesGenerator
|
||||
|
||||
|
||||
def images_extraction(instance):
|
||||
|
@ -12,7 +13,8 @@ def images_extraction(instance):
|
|||
representativeImage = instance.metadata['image']
|
||||
|
||||
# Process Summary:
|
||||
# If summary contains images, extract one to be the representativeImage and remove images from 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:
|
||||
|
@ -20,7 +22,8 @@ def images_extraction(instance):
|
|||
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
|
||||
# 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
|
||||
|
@ -32,6 +35,9 @@ def images_extraction(instance):
|
|||
|
||||
# Set the attribute to content instance
|
||||
instance.featured_image = representativeImage
|
||||
instance.featured_alt = instance.metadata.get('alt', None)
|
||||
instance.featured_link = instance.metadata.get('link', None)
|
||||
instance.featured_caption = instance.metadata.get('caption', None)
|
||||
|
||||
|
||||
def run_plugin(generators):
|
||||
|
@ -39,6 +45,13 @@ def run_plugin(generators):
|
|||
if isinstance(generator, ArticlesGenerator):
|
||||
for article in generator.articles:
|
||||
images_extraction(article)
|
||||
for translation in article.translations:
|
||||
images_extraction(translation)
|
||||
elif isinstance(generator, PagesGenerator):
|
||||
for page in generator.pages:
|
||||
images_extraction(page)
|
||||
for translation in page.translations:
|
||||
images_extraction(translation)
|
||||
|
||||
|
||||
def register():
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
beautifulsoup4
|
|
@ -0,0 +1,73 @@
|
|||
#!/bin/sh
|
||||
import unittest
|
||||
|
||||
import representative_image
|
||||
from jinja2.utils import generate_lorem_ipsum
|
||||
from pelican.contents import Article, Page
|
||||
|
||||
# Generate content with image
|
||||
TEST_CONTENT_IMAGE_URL = 'https://testimage.com/test.jpg'
|
||||
TEST_CONTENT = str(generate_lorem_ipsum(n=3, html=True)) + '<img src="' + TEST_CONTENT_IMAGE_URL + '"/>'+ str(generate_lorem_ipsum(n=2,html=True)) # noqa
|
||||
TEST_SUMMARY_IMAGE_URL = 'https://testimage.com/summary.jpg'
|
||||
TEST_SUMMARY_WITHOUTIMAGE = str(generate_lorem_ipsum(n=1, html=True))
|
||||
TEST_SUMMARY_WITHIMAGE = TEST_SUMMARY_WITHOUTIMAGE + '<img src="' + TEST_SUMMARY_IMAGE_URL + '"/>' # noqa
|
||||
TEST_CUSTOM_IMAGE_URL = 'https://testimage.com/custom.jpg'
|
||||
|
||||
|
||||
class TestRepresentativeImage(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestRepresentativeImage, self).setUp()
|
||||
representative_image.register()
|
||||
|
||||
def test_extract_image_from_content(self):
|
||||
args = {
|
||||
'content': TEST_CONTENT,
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY_WITHOUTIMAGE,
|
||||
},
|
||||
}
|
||||
|
||||
article = Article(**args)
|
||||
self.assertEqual(article.featured_image, TEST_CONTENT_IMAGE_URL)
|
||||
|
||||
def test_extract_image_from_summary(self):
|
||||
args = {
|
||||
'content': TEST_CONTENT,
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY_WITHIMAGE,
|
||||
},
|
||||
}
|
||||
|
||||
article = Article(**args)
|
||||
self.assertEqual(article.featured_image, TEST_SUMMARY_IMAGE_URL)
|
||||
self.assertEqual(article.summary, TEST_SUMMARY_WITHOUTIMAGE)
|
||||
|
||||
def test_extract_image_from_summary_with_custom_image(self):
|
||||
args = {
|
||||
'content': TEST_CONTENT,
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY_WITHIMAGE,
|
||||
'image': TEST_CUSTOM_IMAGE_URL,
|
||||
},
|
||||
}
|
||||
|
||||
article = Article(**args)
|
||||
self.assertEqual(article.featured_image, TEST_CUSTOM_IMAGE_URL)
|
||||
self.assertEqual(article.summary, TEST_SUMMARY_WITHOUTIMAGE)
|
||||
|
||||
def test_extract_image_from_page_summary_with_custom_image(self):
|
||||
args = {
|
||||
'content': TEST_CONTENT,
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY_WITHIMAGE,
|
||||
'image': TEST_CUSTOM_IMAGE_URL,
|
||||
},
|
||||
}
|
||||
page = Page(**args)
|
||||
self.assertEqual(page.featured_image, TEST_CUSTOM_IMAGE_URL)
|
||||
self.assertEqual(page.summary, TEST_SUMMARY_WITHOUTIMAGE)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Ładowanie…
Reference in New Issue