feat: cov.jpg cover image + retina thumbnails
This commit is contained in:
BIN
data/development/brownies-raw/cov.jpg
Normal file
BIN
data/development/brownies-raw/cov.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
data/development/gluten-free-flour/cov.jpg
Normal file
BIN
data/development/gluten-free-flour/cov.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
data/development/vanilla-cut-out-cookies/cov.jpg
Normal file
BIN
data/development/vanilla-cut-out-cookies/cov.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -14,9 +14,7 @@ from .durationcluster import (
|
|||||||
from .ingredients import IngredientsListType
|
from .ingredients import IngredientsListType
|
||||||
from .latex import TexSources, raw_text_to_tex, html_to_tex
|
from .latex import TexSources, raw_text_to_tex, html_to_tex
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from .utils import (
|
from .utils import fillupText, replace_atref_urls, cover_image, noUmlauts
|
||||||
fillupText, replace_atref_urls, sorted_images, title_image, noUmlauts
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MainPlugin(Plugin):
|
class MainPlugin(Plugin):
|
||||||
@@ -33,8 +31,7 @@ class MainPlugin(Plugin):
|
|||||||
'replaceAtRefURLs': replace_atref_urls,
|
'replaceAtRefURLs': replace_atref_urls,
|
||||||
'performGroupBy': self.performGroupBy,
|
'performGroupBy': self.performGroupBy,
|
||||||
|
|
||||||
'sorted_images': sorted_images,
|
'cover_image': cover_image,
|
||||||
'title_image': title_image,
|
|
||||||
|
|
||||||
'latexStr': raw_text_to_tex,
|
'latexStr': raw_text_to_tex,
|
||||||
'latexHtml': html_to_tex,
|
'latexHtml': html_to_tex,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from jinja2.loaders import split_template_path # lookup_template_name()
|
from lektor.context import get_ctx
|
||||||
|
from jinja2.loaders import split_template_path # lookup_template_path()
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
import os
|
import os
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from typing import TYPE_CHECKING, Dict, Union, Optional, List
|
from typing import TYPE_CHECKING, Dict, Union, Optional, Tuple
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lektor.db import Page, Image
|
from lektor.db import Page, Image
|
||||||
from lektor.environment import Environment
|
from lektor.environment import Environment
|
||||||
@@ -51,21 +52,25 @@ def replace_atref_urls(text: str, label: Optional[str] = None) -> str:
|
|||||||
return Markup(' '.join(result))
|
return Markup(' '.join(result))
|
||||||
|
|
||||||
|
|
||||||
def sorted_images(obj: 'Page', attr: str = 'record_label') -> List['Image']:
|
def cover_image(obj: 'Page') -> Optional['Image']:
|
||||||
return sorted(obj.attachments.images,
|
''' Find cover image (cov.jpg or sorted first) and apply thumbnail. '''
|
||||||
key=lambda x: getattr(x, attr)) # type:ignore[no-any-return]
|
best = None
|
||||||
|
for img in obj.attachments.images: # type: Image
|
||||||
|
if img['_id'].rsplit('.')[0] == 'cov':
|
||||||
def title_image(obj: 'Page', attr: str = 'record_label', small: bool = False) \
|
best = img
|
||||||
-> Optional['Image']:
|
break
|
||||||
imgs = sorted_images(obj, attr)
|
if not best or img['_id'] < best['_id']:
|
||||||
img = imgs[0] if imgs else None
|
best = img
|
||||||
if img and small:
|
if best:
|
||||||
img = img.thumbnail(200, 150, mode='crop')
|
ctx = get_ctx()
|
||||||
return img
|
if ctx:
|
||||||
|
ctx.pad.db.track_record_dependency(best)
|
||||||
|
return retina_thumbnail(best, w=200, h=150, mode='crop')[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def noUmlauts(text: str) -> str:
|
def noUmlauts(text: str) -> str:
|
||||||
|
''' Remove umlauts and other unix-path incompatible characters. '''
|
||||||
# try:
|
# try:
|
||||||
# data = unicode(text, 'utf-8')
|
# data = unicode(text, 'utf-8')
|
||||||
# except (TypeError, NameError):
|
# except (TypeError, NameError):
|
||||||
@@ -77,9 +82,47 @@ def noUmlauts(text: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def lookup_template_path(name: str, env: 'Environment') -> Optional[str]:
|
def lookup_template_path(name: str, env: 'Environment') -> Optional[str]:
|
||||||
|
''' Find path to template with name. '''
|
||||||
pieces = split_template_path(name)
|
pieces = split_template_path(name)
|
||||||
for base in env.jinja_env.loader.searchpath:
|
for base in env.jinja_env.loader.searchpath:
|
||||||
path = os.path.join(base, *pieces)
|
path = os.path.join(base, *pieces)
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
return path
|
return path
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def retina_thumbnail(
|
||||||
|
image: 'Image',
|
||||||
|
w: Optional[int] = None,
|
||||||
|
h: Optional[int] = None,
|
||||||
|
*,
|
||||||
|
mode: Optional[str] = None,
|
||||||
|
maxwidth: int = 99999999,
|
||||||
|
retina: int = 2,
|
||||||
|
) -> Tuple['Image', int, int]:
|
||||||
|
''' Constraint an image size with the least possible params. '''
|
||||||
|
if mode:
|
||||||
|
assert w and h, 'if using a crop mode, width and height are mandatory.'
|
||||||
|
else:
|
||||||
|
w = min(w, maxwidth * retina) if (w and w < image.width) else None
|
||||||
|
h = h if (h and h < image.height) else None
|
||||||
|
if w and h:
|
||||||
|
if h >= w / image.width * image.height:
|
||||||
|
h = None
|
||||||
|
else:
|
||||||
|
w = None
|
||||||
|
if w:
|
||||||
|
other = round(w / image.width * image.height)
|
||||||
|
elif h:
|
||||||
|
other = round(h / image.height * image.width)
|
||||||
|
else:
|
||||||
|
return image, image.width, image.height
|
||||||
|
|
||||||
|
ew, eh = w or other, h or other
|
||||||
|
w = (w * retina) if w and (w * retina < image.width) else None
|
||||||
|
h = (h * retina) if h and (h * retina < image.height) else None
|
||||||
|
if not w and not h:
|
||||||
|
return image, ew, eh
|
||||||
|
else:
|
||||||
|
img = image.thumbnail(width=w, height=h, mode=mode, upscale=False)
|
||||||
|
return img, ew, eh
|
||||||
|
|||||||
@@ -18,8 +18,7 @@
|
|||||||
\begin{document}
|
\begin{document}
|
||||||
\makefrontmatter
|
\makefrontmatter
|
||||||
|
|
||||||
{%- for recipe in site.get('/recipes', alt=this.alt).children -%}
|
{%- for recipe in site.get('/recipes', alt=this.alt).children %}
|
||||||
{%- set img = recipe | title_image(small=True) %}
|
|
||||||
|
|
||||||
{# recipe title (start on new page) #}
|
{# recipe title (start on new page) #}
|
||||||
\newrecipe{ {{- recipe._slug -}} }{ {{- recipe.name | latexStr -}} }
|
\newrecipe{ {{- recipe._slug -}} }{ {{- recipe.name | latexStr -}} }
|
||||||
@@ -29,6 +28,7 @@
|
|||||||
\footer{ {{- recipe.source.url | latexStr -}} }{ {{- recipe.source.host | latexStr -}} }
|
\footer{ {{- recipe.source.url | latexStr -}} }{ {{- recipe.source.host | latexStr -}} }
|
||||||
|
|
||||||
{# enumerate ingredients -#}
|
{# enumerate ingredients -#}
|
||||||
|
{%- set img = recipe | cover_image -%}
|
||||||
\begin{ingredients}{ {{- img | url(absolute=True) if img else '' -}} }
|
\begin{ingredients}{ {{- img | url(absolute=True) if img else '' -}} }
|
||||||
|
|
||||||
{%- for ing in recipe.ingredients %}
|
{%- for ing in recipe.ingredients %}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body> {#- ontouchstart="" #}
|
<body> {#- ontouchstart="" #}
|
||||||
<header>
|
<header>
|
||||||
<a id="logo" href="{{ site.get('/', this.alt)|url }}">recipe lekture</a>
|
<a id="logo" href="{{ '/'|url }}">recipe lekture</a>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
{%- set allRecipes = site.get('/recipes', this.alt) %}
|
{%- set allRecipes = site.get('/recipes', this.alt) %}
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
we could use `site.get('/recipes.tex@LatexPDF', alt=this.alt) | url`
|
we could use `site.get('/recipes.tex@LatexPDF', alt=this.alt) | url`
|
||||||
but then this will create a dependency on all recipes
|
but then this will create a dependency on all recipes
|
||||||
-#}
|
-#}
|
||||||
<a href="{{ '/'|url(alt=this.alt) + 'recipes.pdf' }}" download="recipes-{{ this.alt }}.pdf" title="{{ bag('i18n+' + this.alt, 'other.get_pdf') }}">{#--#}
|
<a href="{{ '/'|url + 'recipes.pdf' }}" download="recipes-{{ this.alt }}.pdf" title="{{ bag('i18n+' + this.alt, 'other.get_pdf') }}">{#--#}
|
||||||
<img class="h1em" alt="(PDF)" height="20" src="{{ '/img/icon-pdf.svg'|url }}">{#--#}
|
<img class="h1em" alt="(PDF)" height="20" src="{{ '/img/icon-pdf.svg'|url }}">{#--#}
|
||||||
</a>
|
</a>
|
||||||
{%- set url_without_alt = ('.'|url(absolute=True))[4:] -%}
|
{%- set url_without_alt = ('.'|url(absolute=True))[4:] -%}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{%- macro render_recipe_list(recipes, limit=0) -%}
|
{%- macro render_recipes(recipes) -%}
|
||||||
<div class="tile-grid">
|
<div class="tile-grid">
|
||||||
{%- for recipe in recipes -%}
|
{%- for recipe in recipes -%}
|
||||||
{%- if limit == 0 or loop.index <= limit -%}
|
|
||||||
{%- set img = recipe | title_image(small=True) -%}
|
|
||||||
<a href="{{ recipe|url }}">{#--#}
|
<a href="{{ recipe|url }}">{#--#}
|
||||||
<div class="recipe-tile">
|
<div class="recipe-tile">
|
||||||
|
|
||||||
@@ -15,10 +13,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#- show image or placeholder text #}
|
{#- show image or placeholder text -#}
|
||||||
{% if img -%}
|
{%- set img = recipe | cover_image -%}
|
||||||
|
{%- if img %}
|
||||||
<img class="lozad" data-src="{{ img|url }}">
|
<img class="lozad" data-src="{{ img|url }}">
|
||||||
{%- else -%}
|
{%- else %}
|
||||||
<div class="placeholder">No Image</div>
|
<div class="placeholder">No Image</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@
|
|||||||
<p>{{ recipe.name }}</p>
|
<p>{{ recipe.name }}</p>
|
||||||
</div>{#--#}
|
</div>{#--#}
|
||||||
</a>
|
</a>
|
||||||
{%- endif -%}
|
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</div>
|
</div>
|
||||||
{%- endmacro -%}
|
{%- endmacro -%}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
<article class="recipe">
|
<article class="recipe">
|
||||||
<!-- date added: {{ this.date }} -->
|
<!-- date added: {{ this.date }} -->
|
||||||
<section id="img-carousel" class="v-scroll center">
|
<section id="img-carousel" class="v-scroll center">
|
||||||
{%- for img in this | sorted_images %}
|
{%- for img in this.attachments.images | sort(attribute='_id')
|
||||||
|
if img._id.rsplit('.')[0] != 'cov' %}
|
||||||
<img class="lozad" data-src="{{ img|url }}" height="400">
|
<img class="lozad" data-src="{{ img|url }}" height="400">
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% from "macros/recipes.html" import render_recipe_list %}
|
{% from "macros/recipes.html" import render_recipes %}
|
||||||
{% from "macros/pagination.html" import render_pagination_all %}
|
{% from "macros/pagination.html" import render_pagination_all %}
|
||||||
{% block title %}{{ bag('i18n+' + this.alt, 'title.recipes') }}{% endblock %}
|
{% block title %}{{ bag('i18n+' + this.alt, 'title.recipes') }}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
}} <a href="/static/pdf-{{ this.alt }}.pdf" download="recipes-{{ this.alt }}.pdf" title="{{
|
}} <a href="/static/pdf-{{ this.alt }}.pdf" download="recipes-{{ this.alt }}.pdf" title="{{
|
||||||
bag('i18n+' + this.alt, 'other.get_pdf')
|
bag('i18n+' + this.alt, 'other.get_pdf')
|
||||||
}}"><img class="h1em" alt="(PDF)" height="20pt" src="{{ '/img/icon-pdf.svg'|url }}"></a></h1>
|
}}"><img class="h1em" alt="(PDF)" height="20pt" src="{{ '/img/icon-pdf.svg'|url }}"></a></h1>
|
||||||
{{ render_recipe_list(this.pagination.items) }}
|
{{ render_recipes(this.pagination.items) }}
|
||||||
{{ render_pagination_all(this.pagination) }}
|
{{ render_pagination_all(this.pagination) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% from "macros/recipes.html" import render_recipe_list %}
|
{% from "macros/recipes.html" import render_recipes %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>{{ bag('i18n+' + this.alt, 'title.latest') }}</h1>
|
<h1>{{ bag('i18n+' + this.alt, 'title.latest') }}</h1>
|
||||||
<div class="latest">
|
<div class="latest">
|
||||||
{{ render_recipe_list(site.query('recipes', this.alt).order_by('-date', 'name'), limit=6) }}
|
{{ render_recipes(site.query('/recipes', this.alt).order_by('-date', 'name').limit(6)) }}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% from "macros/recipes.html" import render_recipe_list %}
|
{% from "macros/recipes.html" import render_recipes %}
|
||||||
{% from "macros/pagination.html" import render_pagination_all %}
|
{% from "macros/pagination.html" import render_pagination_all %}
|
||||||
{% block title %}{{ this.name }}{% endblock %}
|
{% block title %}{{ this.name }}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Tag: {{ this.name }}</h1>
|
<h1>Tag: {{ this.name }}</h1>
|
||||||
{{ render_recipe_list(this.pagination.items) }}
|
{{ render_recipes(this.pagination.items) }}
|
||||||
{{ render_pagination_all(this.pagination) }}
|
{{ render_pagination_all(this.pagination) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user