edited templates
This commit is contained in:
parent
f2a3d7ada4
commit
2690f0af87
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
|
||||
<title>{{slug}}</title>
|
||||
<title>{{name}}</title>
|
||||
<link rel="stylesheet" href="/static/bulma.min.css" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
@ -16,7 +16,7 @@
|
||||
>
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="#">
|
||||
<strong>Static Translated Site Generator</strong>
|
||||
<strong>{{name}}</strong>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
@ -33,7 +33,7 @@
|
||||
<div class="card mb-4" lang="{{t.language.code}}" style="height: 100%;">
|
||||
<a href="{{t.url}}" hreflang="{{t.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
|
||||
<div class="card-content">
|
||||
<p class="title">{{t.language.flag}} {{t.title}}</p>
|
||||
<p class="title">{{t.language.flag}} {{t.name}}</p>
|
||||
<hr />
|
||||
<p class="content">
|
||||
{{t.preview}}
|
||||
@ -41,7 +41,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<time class="card-footer-item" datetime="{{iso_date}}">{{date}}</time>
|
||||
<time class="card-footer-item" datetime="{{t.iso_date}}">{{t.date}}</time>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -62,15 +62,15 @@
|
||||
<div class="card mb-4" >
|
||||
<a href="{{c.url}}" class="card mb-4" style="color: inherit; text-decoration: none;">
|
||||
<div class="card-content">
|
||||
<p class="title">{{c.slug}} </p>
|
||||
<p class="title">{{c.name}} </p>
|
||||
<hr />
|
||||
<p class="content is-flex is-flex-direction-column" style="gap: 10px;">
|
||||
{% for ct in c.translations %}
|
||||
<a href="{{ct.url}}" hreflang="{{ct.language.code}}">{{ct.language.flag}}: {{ct.title}}</a>
|
||||
<a href="{{ct.url}}" hreflang="{{ct.language.code}}">{{ct.language.flag}}: {{ct.name}}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<hr />
|
||||
<time datetime="{{iso_date}}">{{date}}</time>
|
||||
<time datetime="{{c.iso_date}}">{{c.date}}</time>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -83,7 +83,7 @@
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>STSG</strong> by Hazel. © 2025</p>
|
||||
<p><strong>{{name}}</strong> by {{author}}. © 2025</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
|
||||
<title>{{title}}</title>
|
||||
<title>{{name}}</title>
|
||||
<link rel="stylesheet" href="/static/bulma.min.css" />
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
@ -18,7 +18,7 @@
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="{{article_url}}">
|
||||
<strong>{{language.flag}} {{title}}</strong>
|
||||
<time datetime="{{meta.iso_date}}">{{meta.date}}</time>
|
||||
<time datetime="{{iso_date}}">{{date}}</time>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
@ -39,7 +39,7 @@
|
||||
<div class="card mb-4" >
|
||||
<a href="{{c.url}}" hreflang="{{c.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
|
||||
<div class="card-content">
|
||||
<p class="title">{{c.title}}</p>
|
||||
<p class="title">{{c.name}}</p>
|
||||
<hr />
|
||||
<p class="content">
|
||||
{{c.preview}}
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<time class="card-footer-item" datetime="{{c.meta.iso_date}}">{{c.meta.date}}</time>
|
||||
<time class="card-footer-item" datetime="{{c.iso_date}}">{{c.date}}</time>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -61,7 +61,7 @@
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>STSG</strong> by Hazel. © 2025</p>
|
||||
<p><strong>{{name}}</strong> by {{author}}. © 2025</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
@ -1,3 +1,5 @@
|
||||
fall_back_to_overview_in_translation = false
|
||||
|
||||
[setup]
|
||||
source_directory = "src"
|
||||
dist_directory = "dist"
|
||||
|
@ -1,5 +1,6 @@
|
||||
class config:
|
||||
default_author = "anonymous"
|
||||
fall_back_to_overview_in_translation = True
|
||||
|
||||
class setup:
|
||||
source_directory = "src"
|
||||
|
157
stsg/build.py
157
stsg/build.py
@ -6,7 +6,7 @@ import os
|
||||
from markdown2 import markdown
|
||||
from typing import Optional, Union, Dict, Generator, List, DefaultDict, Any, TypedDict, Set
|
||||
from bs4 import BeautifulSoup
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, UserList
|
||||
import toml
|
||||
from datetime import datetime
|
||||
import jinja2
|
||||
@ -17,16 +17,6 @@ from .definitions import *
|
||||
from . import config
|
||||
|
||||
|
||||
def get_first_header_content(content, fallback: str = ""):
|
||||
soup = BeautifulSoup(content, 'html.parser')
|
||||
for level in range(1, 7):
|
||||
header = soup.find(f'h{level}')
|
||||
if header:
|
||||
return header.get_text(strip=True)
|
||||
|
||||
return fallback
|
||||
|
||||
|
||||
def shorten_text_and_clean(html_string, max_length=config.formatting.preview_length):
|
||||
soup = BeautifulSoup(html_string, 'html.parser')
|
||||
|
||||
@ -128,10 +118,62 @@ def add_html_link(c):
|
||||
c["link"] = f'<a href="{url}">{name}</a>'
|
||||
|
||||
|
||||
def get_translated_articles(articles: List[Article], language_code: str = None) -> List[Union[ArticleTranslation, Article]]:
|
||||
result = {}
|
||||
|
||||
for a in articles:
|
||||
if a.slug in result:
|
||||
continue
|
||||
|
||||
if language_code is None:
|
||||
result[a.slug] = a
|
||||
continue
|
||||
|
||||
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
|
||||
continue
|
||||
|
||||
result[a.slug] = a.article_translations_map.get(language_code, a)
|
||||
|
||||
|
||||
class ArticleList(UserList):
|
||||
def __init__(self, iterable):
|
||||
super().__init__(item for item in iterable)
|
||||
|
||||
self.used_slugs = set()
|
||||
|
||||
def append(self, a: Union[Article, str]):
|
||||
if isinstance(a, str):
|
||||
a = ARTICLE_LAKE[a]
|
||||
|
||||
if a.slug in self.used_slugs:
|
||||
return
|
||||
|
||||
self.used_slugs.add(a.slug)
|
||||
self.data.append(a)
|
||||
|
||||
def extend(self, other):
|
||||
for a in other:
|
||||
self.append(a)
|
||||
|
||||
def get_translated(self, language_code: str) -> ArticleList[Union[ArticleTranslation, Article]]:
|
||||
res = ArticleList([])
|
||||
|
||||
for a in self:
|
||||
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
|
||||
continue
|
||||
|
||||
res.append(a.article_translations_map.get(language_code, a))
|
||||
|
||||
return res
|
||||
|
||||
@property
|
||||
def context(self) -> List[Union[ArticleContext, ArticleTranslationContext]]:
|
||||
return [a.context for a in self]
|
||||
|
||||
|
||||
class ArticleTranslation:
|
||||
article: Article
|
||||
slug: str = property(fget=lambda self: self.article.slug)
|
||||
file: Path
|
||||
|
||||
@cached_property
|
||||
@ -178,31 +220,32 @@ class ArticleTranslation:
|
||||
self.article = article
|
||||
self.file = file
|
||||
|
||||
self.context = {}
|
||||
self.cross_article_context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
|
||||
self.context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
|
||||
|
||||
@cached_property
|
||||
def name(self) -> str:
|
||||
soup = BeautifulSoup(self.html_content, 'html.parser')
|
||||
for level in range(1, 7):
|
||||
header = soup.find(f'h{level}')
|
||||
if header:
|
||||
return header.get_text(strip=True)
|
||||
|
||||
return self.article.name
|
||||
|
||||
def __init_context__(self):
|
||||
self.context["meta"] = self.article.context_shared
|
||||
self.context["slug"] = self.article.slug
|
||||
self.context["name"] = self.name
|
||||
self.context["url"] = self.url
|
||||
add_html_link(self.context)
|
||||
self.context["date"] = self.article.modified_at.strftime(config.formatting.datetime_format)
|
||||
self.context["iso_date"] = self.article.modified_at.isoformat()
|
||||
self.context["author"] = self.article.author
|
||||
|
||||
self.context["language"] = LANGUAGES[self.language_code]
|
||||
self.context["article_url"] = self.article.url
|
||||
self.context["title"] = get_first_header_content(self.html_content, fallback=LANGUAGES[self.language_code]["native_name"])
|
||||
|
||||
self.cross_article_context.update(self.article.context_shared)
|
||||
self.cross_article_context["title"] = self.context["title"]
|
||||
self.cross_article_context["article_url"] = self.article.url
|
||||
self.cross_article_context["url"] = self.url
|
||||
add_html_link(self.cross_article_context)
|
||||
|
||||
# get children
|
||||
self.context["children"] = [
|
||||
c.article_translations_map[self.language_code].context for c in self.article.child_articles
|
||||
if self.language_code in c.article_translations_map
|
||||
]
|
||||
|
||||
self.linked_context = self.context["linked"] = []
|
||||
self.related_context = self.context["related"] = []
|
||||
self.context["children"] = self.article.child_articles.get_translated(self.language_code).context
|
||||
|
||||
def __init_content_context__(self):
|
||||
template = jinja2.Template(self.html_content)
|
||||
@ -216,17 +259,16 @@ class ArticleTranslation:
|
||||
|
||||
template.environment.context_class = jinja2.runtime.Context
|
||||
accessed_keys = template.environment.accessed_keys
|
||||
for key in accessed_keys:
|
||||
a = ARTICLE_LAKE[key]
|
||||
if self.language_code in a.article_translations_map:
|
||||
self.linked_context.append(a.article_translations_map[self.language_code].context)
|
||||
|
||||
self.related_context.extend(self.linked_context)
|
||||
self.related_context.extend(self.context["children"])
|
||||
for key in accessed_keys:
|
||||
self.article.linked_articles.append(key)
|
||||
|
||||
self.context["content"] = self.html_content
|
||||
self.context["preview"] = get_preview_text(html_string=self.html_content)
|
||||
|
||||
self.context["linked"] = self.article.linked_articles.get_translated(self.language_code).context
|
||||
self.context["related"] = self.article.related_articles.get_translated(self.language_code).context
|
||||
|
||||
def build(self):
|
||||
self.dist_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@ -272,25 +314,17 @@ class Article:
|
||||
context_shared: Dict[str, Any]
|
||||
cross_article_context: Dict[str, Any]
|
||||
|
||||
child_articles: List[Article]
|
||||
article_translations_list: List[ArticleTranslation]
|
||||
child_articles: ArticleList[Article]
|
||||
article_translations_list: ArticleList[ArticleTranslation]
|
||||
article_translations_map: Dict[str, ArticleTranslation]
|
||||
|
||||
linked_articles: List[Article]
|
||||
linked_articles: ArticleList[Article]
|
||||
|
||||
@cached_property
|
||||
def related_articles(self) -> List[Article]:
|
||||
used_slugs = set()
|
||||
related = []
|
||||
|
||||
for a in [*self.child_articles, *self.linked_articles]:
|
||||
if a.slug in used_slugs:
|
||||
continue
|
||||
|
||||
used_slugs.add(a.slug)
|
||||
related.append(a)
|
||||
|
||||
return related
|
||||
def related_articles(self) -> ArticleList[Article]:
|
||||
res = ArticleList(self.child_articles)
|
||||
res.extend(self.linked_articles)
|
||||
return res
|
||||
|
||||
def __init__(self, directory: Path, article_path: Optional[List[str]] = None, is_root: bool = False, parent: Optional[Article] = None):
|
||||
self.directory = directory
|
||||
@ -298,17 +332,15 @@ class Article:
|
||||
self.article_path: List[Article] = article_path or []
|
||||
self.article_path.append(self)
|
||||
|
||||
self.context: ArticleContext = {}
|
||||
self.context_shared = {}
|
||||
self.cross_article_context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
|
||||
self.context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
|
||||
|
||||
ARTICLE_LAKE[self.slug] = self
|
||||
|
||||
self.linked_articles = []
|
||||
self.linked_articles = ArticleList([])
|
||||
|
||||
# build the tree
|
||||
self.child_articles = []
|
||||
self.article_translations_list = []
|
||||
self.child_articles = ArticleList([])
|
||||
self.article_translations_list = ArticleList([])
|
||||
self.article_translations_map = {}
|
||||
|
||||
for c in self.directory.iterdir():
|
||||
@ -356,25 +388,24 @@ class Article:
|
||||
self.context["author"] = self.author
|
||||
|
||||
# recursive context structures
|
||||
self.context["translations"] = [t.context for t in self.self.article_translations_list]
|
||||
self.context["children"] = [c.context for c in self.child_articles]
|
||||
self.context["translations"] = self.article_translations_list.context
|
||||
self.context["children"] = self.child_articles.context
|
||||
for lang, article in self.article_translations_map.items():
|
||||
self.context[lang] = article.context
|
||||
|
||||
for at in self.article_translations_list:
|
||||
at.__init_context__()
|
||||
|
||||
# the __init_context__ functions of the translations needs to be called first
|
||||
# because the linked articles will be set there
|
||||
self.context["linked"] = [l.context for l in self.linked_articles]
|
||||
self.context["related"] = [r.context for r in self.related_articles]
|
||||
|
||||
for a in self.child_articles:
|
||||
a.__init_context__()
|
||||
|
||||
def __init_content_context__(self):
|
||||
for at in self.article_translations_list:
|
||||
at.__init_content_context__()
|
||||
|
||||
self.context["linked"] = self.linked_articles.context
|
||||
self.context["related"] = self.related_articles.context
|
||||
|
||||
for a in self.child_articles:
|
||||
a.__init_content_context__()
|
||||
|
||||
@ -400,9 +431,9 @@ class ContextDict(jinja2.runtime.Context):
|
||||
|
||||
# GLOBALS
|
||||
logger = logging.getLogger("stsg.build")
|
||||
ARTICLE_LAKE: Dict[str, Article] = {}
|
||||
CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Any]] = {}
|
||||
TRANSLATED_CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Dict[str, Any]]] = defaultdict(dict)
|
||||
ARTICLE_LAKE: Dict[str, Article] = {}
|
||||
ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import TypedDict, List
|
||||
from typing import TypedDict, List, Union
|
||||
|
||||
|
||||
class ArticleConfig(TypedDict):
|
||||
@ -24,6 +24,14 @@ class ArticleContext(TypedDict):
|
||||
related: List[ArticleContext]
|
||||
|
||||
|
||||
class TypedLanguage(TypedDict):
|
||||
flag: str
|
||||
name: str
|
||||
native_name: str
|
||||
priority: int
|
||||
code: str
|
||||
|
||||
|
||||
class ArticleTranslationContext(TypedDict):
|
||||
slug: str
|
||||
name: str
|
||||
@ -33,7 +41,18 @@ class ArticleTranslationContext(TypedDict):
|
||||
iso_date: str
|
||||
author: str
|
||||
|
||||
translations: List[ArticleTranslationContext]
|
||||
children: List[ArticleTranslationContext]
|
||||
linked: List[ArticleTranslationContext]
|
||||
related: List[ArticleTranslationContext]
|
||||
language: TypedLanguage
|
||||
article_url: str
|
||||
"""
|
||||
The type Union[ArticleTranslationContext, ArticleContext] exist,
|
||||
because if the article it is linked to doesn't exist in the same languages it uses the overview instead.
|
||||
If you dislike this behavior set:
|
||||
config.fall_back_to_overview_in_translation = False
|
||||
"""
|
||||
children: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||
|
||||
# you can't use these within the markdown text itself
|
||||
content: str
|
||||
preview: str
|
||||
linked: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||
related: List[Union[ArticleTranslationContext, ArticleContext]]
|
||||
|
Loading…
x
Reference in New Issue
Block a user