Compare commits
No commits in common. "main" and "v0.2.12" have entirely different histories.
35 changed files with 201 additions and 418 deletions
CHANGELOG.mdREADME.md
madblog
__init__.pyapp.pyconfig.pylatex.pyroutes.py
pyproject.tomlsetup.cfgsetup.pystatic
css
fonts
Lora-Bold.eotLora-Bold.ttfLora-Bold.woffLora-Bold.woff2Lora-Italic.eotLora-Italic.ttfLora-Italic.woffLora-Italic.woff2Lora-Regular.eotLora-Regular.ttfLora-Regular.woffLora-Regular.woff2lora.css
img
templates
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,28 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.2.35
|
|
||||||
|
|
||||||
- Use _Lora_ font for the article body.
|
|
||||||
|
|
||||||
## 0.2.24
|
|
||||||
|
|
||||||
- Better default fonts - `sans-serif` style for the index and the titles,
|
|
||||||
`serif` for the articles' body.
|
|
||||||
|
|
||||||
## 0.2.19
|
|
||||||
|
|
||||||
- Added `short_feed` configuration flag to permanently disable returning the
|
|
||||||
full content of the articles in the RSS feed.
|
|
||||||
|
|
||||||
## 0.2.16
|
|
||||||
|
|
||||||
- Removed `alt` attribute from LaTeX rendered `<img>` tags. It may generate
|
|
||||||
non-standard Unicode characters that break the RSS feed.
|
|
||||||
|
|
||||||
## 0.2.14
|
|
||||||
|
|
||||||
- Better support for PWA tags and added a default config-generated `/manifest.json`.
|
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
- Fix for broken RSS feed URLs when a blog has no pages.
|
- Fix for broken RSS feed URLs when a blog has no pages.
|
||||||
|
|
|
@ -72,8 +72,6 @@ logo: /path/or/url/here
|
||||||
language: en-US
|
language: en-US
|
||||||
# Show/hide the header (default: true)
|
# Show/hide the header (default: true)
|
||||||
header: true
|
header: true
|
||||||
# Enable/disable the short RSS feed (default: false)
|
|
||||||
short_feed: false
|
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
- category1
|
- category1
|
||||||
|
@ -135,9 +133,4 @@ $$
|
||||||
RSS feeds for the blog are provided under the `/rss` URL.
|
RSS feeds for the blog are provided under the `/rss` URL.
|
||||||
|
|
||||||
By default, the whole HTML-rendered content of an article is returned under `rss.channel.item.description`.
|
By default, the whole HTML-rendered content of an article is returned under `rss.channel.item.description`.
|
||||||
|
|
||||||
If you only want to include the short description of an article in the feed, use `/rss?short` instead.
|
If you only want to include the short description of an article in the feed, use `/rss?short` instead.
|
||||||
|
|
||||||
If you want the short feed (i.e. without the fully rendered article as a
|
|
||||||
description) to be always returned, then you can specify `short_feed=true` in
|
|
||||||
your configuration.
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.2.35"
|
__version__ = '0.2.12'
|
||||||
|
|
106
madblog/app.py
106
madblog/app.py
|
@ -12,11 +12,11 @@ from ._sorters import PagesSorter, PagesSortByTime
|
||||||
|
|
||||||
|
|
||||||
class BlogApp(Flask):
|
class BlogApp(Flask):
|
||||||
_title_header_regex = re.compile(r"^#\s*((\[(.*)\])|(.*))")
|
_title_header_regex = re.compile(r'^#\s*((\[(.*)\])|(.*))')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, template_folder=config.templates_dir, **kwargs)
|
super().__init__(*args, template_folder=config.templates_dir, **kwargs)
|
||||||
self.pages_dir = os.path.join(config.content_dir, "markdown")
|
self.pages_dir = os.path.join(config.content_dir, 'markdown')
|
||||||
self.img_dir = config.default_img_dir
|
self.img_dir = config.default_img_dir
|
||||||
self.css_dir = config.default_css_dir
|
self.css_dir = config.default_css_dir
|
||||||
self.js_dir = config.default_js_dir
|
self.js_dir = config.default_js_dir
|
||||||
|
@ -27,77 +27,73 @@ class BlogApp(Flask):
|
||||||
# `config.content_dir` is treated as the root for markdown files.
|
# `config.content_dir` is treated as the root for markdown files.
|
||||||
self.pages_dir = config.content_dir
|
self.pages_dir = config.content_dir
|
||||||
|
|
||||||
img_dir = os.path.join(config.content_dir, "img")
|
img_dir = os.path.join(config.content_dir, 'img')
|
||||||
if os.path.isdir(img_dir):
|
if os.path.isdir(img_dir):
|
||||||
self.img_dir = os.path.abspath(img_dir)
|
self.img_dir = os.path.abspath(img_dir)
|
||||||
else:
|
else:
|
||||||
self.img_dir = config.content_dir
|
self.img_dir = config.content_dir
|
||||||
|
|
||||||
css_dir = os.path.join(config.content_dir, "css")
|
css_dir = os.path.join(config.content_dir, 'css')
|
||||||
if os.path.isdir(css_dir):
|
if os.path.isdir(css_dir):
|
||||||
self.css_dir = os.path.abspath(css_dir)
|
self.css_dir = os.path.abspath(css_dir)
|
||||||
|
|
||||||
js_dir = os.path.join(config.content_dir, "js")
|
js_dir = os.path.join(config.content_dir, 'js')
|
||||||
if os.path.isdir(js_dir):
|
if os.path.isdir(js_dir):
|
||||||
self.js_dir = os.path.abspath(js_dir)
|
self.js_dir = os.path.abspath(js_dir)
|
||||||
|
|
||||||
fonts_dir = os.path.join(config.content_dir, "fonts")
|
fonts_dir = os.path.join(config.content_dir, 'fonts')
|
||||||
if os.path.isdir(fonts_dir):
|
if os.path.isdir(fonts_dir):
|
||||||
self.fonts_dir = os.path.abspath(fonts_dir)
|
self.fonts_dir = os.path.abspath(fonts_dir)
|
||||||
|
|
||||||
templates_dir = os.path.join(config.content_dir, "templates")
|
templates_dir = os.path.join(config.content_dir, 'templates')
|
||||||
if os.path.isdir(templates_dir):
|
if os.path.isdir(templates_dir):
|
||||||
self.template_folder = os.path.abspath(templates_dir)
|
self.template_folder = os.path.abspath(templates_dir)
|
||||||
|
|
||||||
def get_page_metadata(self, page: str) -> dict:
|
def get_page_metadata(self, page: str) -> dict:
|
||||||
if not page.endswith(".md"):
|
if not page.endswith('.md'):
|
||||||
page = page + ".md"
|
page = page + '.md'
|
||||||
|
|
||||||
md_file = os.path.join(self.pages_dir, page)
|
md_file = os.path.join(self.pages_dir, page)
|
||||||
if not os.path.isfile(md_file):
|
if not os.path.isfile(md_file):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
metadata = {}
|
metadata = {}
|
||||||
with open(md_file, "r") as f:
|
with open(md_file, 'r') as f:
|
||||||
metadata["uri"] = "/article/" + page[:-3]
|
metadata['uri'] = '/article/' + page[:-3]
|
||||||
|
|
||||||
for line in f:
|
for line in f.readlines():
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not (m := re.match(r"^\[//]: # \(([^:]+):\s*(.*)\)\s*$", line)):
|
if not (m := re.match(r'^\[//]: # \(([^:]+):\s*([^)]+)\)\s*$', line)):
|
||||||
break
|
break
|
||||||
|
|
||||||
if m.group(1) == "published":
|
if m.group(1) == 'published':
|
||||||
metadata[m.group(1)] = datetime.datetime.fromisoformat(
|
metadata[m.group(1)] = datetime.date.fromisoformat(m.group(2))
|
||||||
m.group(2)
|
|
||||||
).date()
|
|
||||||
else:
|
else:
|
||||||
metadata[m.group(1)] = m.group(2)
|
metadata[m.group(1)] = m.group(2)
|
||||||
|
|
||||||
if not metadata.get("title"):
|
if not metadata.get('title'):
|
||||||
# If the `title` header isn't available in the file,
|
# If the `title` header isn't available in the file,
|
||||||
# infer it from the first line of the file
|
# infer it from the first line of the file
|
||||||
with open(md_file, "r") as f:
|
with open(md_file, 'r') as f:
|
||||||
header = ""
|
header = ''
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
header = line
|
header = line
|
||||||
break
|
break
|
||||||
|
|
||||||
metadata["title_inferred"] = True
|
metadata['title_inferred'] = True
|
||||||
m = self._title_header_regex.search(header)
|
m = self._title_header_regex.search(header)
|
||||||
if m:
|
if m:
|
||||||
metadata["title"] = m.group(3) or m.group(1)
|
metadata['title'] = m.group(3) or m.group(1)
|
||||||
else:
|
else:
|
||||||
metadata["title"] = os.path.basename(md_file)
|
metadata['title'] = os.path.basename(md_file)
|
||||||
|
|
||||||
if not metadata.get("published"):
|
if not metadata.get('published'):
|
||||||
# If the `published` header isn't available in the file,
|
# If the `published` header isn't available in the file,
|
||||||
# infer it from the file's creation date
|
# infer it from the file's creation date
|
||||||
metadata["published"] = datetime.date.fromtimestamp(
|
metadata['published'] = datetime.date.fromtimestamp(os.stat(md_file).st_ctime)
|
||||||
os.stat(md_file).st_ctime
|
metadata['published_inferred'] = True
|
||||||
)
|
|
||||||
metadata["published_inferred"] = True
|
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
@ -106,42 +102,37 @@ class BlogApp(Flask):
|
||||||
page: str,
|
page: str,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
skip_header: bool = False,
|
skip_header: bool = False,
|
||||||
skip_html_head: bool = False,
|
skip_html_head: bool = False
|
||||||
):
|
):
|
||||||
if not page.endswith(".md"):
|
if not page.endswith('.md'):
|
||||||
page = page + ".md"
|
page = page + '.md'
|
||||||
|
|
||||||
metadata = self.get_page_metadata(page)
|
metadata = self.get_page_metadata(page)
|
||||||
# Don't duplicate the page title if it's been inferred
|
# Don't duplicate the page title if it's been inferred
|
||||||
if not (title or metadata.get("title_inferred")):
|
if not (title or metadata.get('title_inferred')):
|
||||||
title = metadata.get("title", config.title)
|
title = metadata.get('title', config.title)
|
||||||
|
|
||||||
with open(os.path.join(self.pages_dir, page), "r") as f:
|
with open(os.path.join(self.pages_dir, page), 'r') as f:
|
||||||
return render_template(
|
return render_template(
|
||||||
"article.html",
|
'article.html',
|
||||||
config=config,
|
config=config,
|
||||||
title=title,
|
title=title,
|
||||||
image=metadata.get("image"),
|
image=metadata.get('image'),
|
||||||
description=metadata.get("description"),
|
description=metadata.get('description'),
|
||||||
author=(
|
author=(
|
||||||
re.match(r"(.+?)\s+<([^>]+>)", metadata["author"])[1]
|
re.match(r'(.+?)\s+<([^>]+>)', metadata['author'])[1]
|
||||||
if "author" in metadata
|
if 'author' in metadata else None
|
||||||
else None
|
|
||||||
),
|
),
|
||||||
author_email=(
|
author_email=(
|
||||||
re.match(r"(.+?)\s+<([^>]+)>", metadata["author"])[2]
|
re.match(r'(.+?)\s+<([^>]+)>', metadata['author'])[2]
|
||||||
if "author" in metadata
|
if 'author' in metadata else None
|
||||||
else None
|
|
||||||
),
|
),
|
||||||
published=(
|
published=(
|
||||||
metadata["published"].strftime("%b %d, %Y")
|
metadata['published'].strftime('%b %d, %Y')
|
||||||
if metadata.get("published")
|
if metadata.get('published') and not metadata.get('published_inferred')
|
||||||
and not metadata.get("published_inferred")
|
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
content=markdown(
|
content=markdown(f.read(), extensions=['fenced_code', 'codehilite', MarkdownLatex()]),
|
||||||
f.read(), extensions=["fenced_code", "codehilite", "tables", MarkdownLatex()]
|
|
||||||
),
|
|
||||||
skip_header=skip_header,
|
skip_header=skip_header,
|
||||||
skip_html_head=skip_html_head,
|
skip_html_head=skip_html_head,
|
||||||
)
|
)
|
||||||
|
@ -154,25 +145,26 @@ class BlogApp(Flask):
|
||||||
sorter: Type[PagesSorter] = PagesSortByTime,
|
sorter: Type[PagesSorter] = PagesSortByTime,
|
||||||
reverse: bool = True,
|
reverse: bool = True,
|
||||||
) -> List[Tuple[int, dict]]:
|
) -> List[Tuple[int, dict]]:
|
||||||
pages_dir = app.pages_dir.rstrip("/")
|
pages_dir = app.pages_dir.rstrip('/')
|
||||||
pages = [
|
pages = [
|
||||||
{
|
{
|
||||||
"path": os.path.join(root[len(pages_dir) + 1 :], f),
|
'path': os.path.join(root[len(pages_dir)+1:], f),
|
||||||
"folder": root[len(pages_dir) + 1 :],
|
'folder': root[len(pages_dir)+1:],
|
||||||
"content": (
|
'content': (
|
||||||
self.get_page(
|
self.get_page(
|
||||||
os.path.join(root, f),
|
os.path.join(root, f),
|
||||||
skip_header=skip_header,
|
skip_header=skip_header,
|
||||||
skip_html_head=skip_html_head,
|
skip_html_head=skip_html_head,
|
||||||
)
|
)
|
||||||
if with_content
|
if with_content else ''
|
||||||
else ""
|
),
|
||||||
|
**self.get_page_metadata(
|
||||||
|
os.path.join(root[len(pages_dir)+1:], f)
|
||||||
),
|
),
|
||||||
**self.get_page_metadata(os.path.join(root[len(pages_dir) + 1 :], f)),
|
|
||||||
}
|
}
|
||||||
for root, _, files in os.walk(pages_dir, followlinks=True)
|
for root, _, files in os.walk(pages_dir, followlinks=True)
|
||||||
for f in files
|
for f in files
|
||||||
if f.endswith(".md")
|
if f.endswith('.md')
|
||||||
]
|
]
|
||||||
|
|
||||||
sorter_func = sorter(pages)
|
sorter_func = sorter(pages)
|
||||||
|
|
|
@ -1,61 +1,57 @@
|
||||||
import os
|
import os
|
||||||
from typing import List
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
title = "Blog"
|
title = 'Blog'
|
||||||
description = ""
|
description = ''
|
||||||
link = "/"
|
link = '/'
|
||||||
home_link = "/"
|
home_link = '/'
|
||||||
language = "en-US"
|
language = 'en-US'
|
||||||
logo = "/img/icon.png"
|
logo = '/img/icon.png'
|
||||||
header = True
|
header = True
|
||||||
content_dir = "."
|
content_dir = '.'
|
||||||
categories: List[str] = field(default_factory=list)
|
categories = None
|
||||||
short_feed = False
|
|
||||||
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
templates_dir = os.path.join(basedir, "templates")
|
templates_dir = os.path.join(basedir, 'templates')
|
||||||
static_dir = os.path.join(basedir, "static")
|
static_dir = os.path.join(basedir, 'static')
|
||||||
default_css_dir = os.path.join(static_dir, "css")
|
default_css_dir = os.path.join(static_dir, 'css')
|
||||||
default_js_dir = os.path.join(static_dir, "js")
|
default_js_dir = os.path.join(static_dir, 'js')
|
||||||
default_fonts_dir = os.path.join(static_dir, "fonts")
|
default_fonts_dir = os.path.join(static_dir, 'fonts')
|
||||||
default_img_dir = os.path.join(static_dir, "img")
|
default_img_dir = os.path.join(static_dir, 'img')
|
||||||
|
|
||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
|
|
||||||
def init_config(content_dir=".", config_file="config.yaml"):
|
def init_config(content_dir='.', config_file='config.yaml'):
|
||||||
cfg = {}
|
cfg = {}
|
||||||
config.content_dir = content_dir
|
config.content_dir = content_dir
|
||||||
|
|
||||||
if os.path.isfile(config_file):
|
if os.path.isfile(config_file):
|
||||||
with open(config_file, "r") as f:
|
with open(config_file, 'r') as f:
|
||||||
cfg = yaml.safe_load(f)
|
cfg = yaml.safe_load(f)
|
||||||
|
|
||||||
if cfg.get("title"):
|
if cfg.get('title'):
|
||||||
config.title = cfg["title"]
|
config.title = cfg['title']
|
||||||
if cfg.get("description"):
|
if cfg.get('description'):
|
||||||
config.description = cfg["description"]
|
config.description = cfg['description']
|
||||||
if cfg.get("link"):
|
if cfg.get('link'):
|
||||||
config.link = cfg["link"]
|
config.link = cfg['link']
|
||||||
if cfg.get("home_link"):
|
if cfg.get('home_link'):
|
||||||
config.home_link = cfg["home_link"]
|
config.home_link = cfg['home_link']
|
||||||
if cfg.get("logo") is not None:
|
if cfg.get('logo') is not None:
|
||||||
config.logo = cfg["logo"]
|
config.logo = cfg['logo']
|
||||||
if cfg.get("language"):
|
if cfg.get('language'):
|
||||||
config.language = cfg["language"]
|
config.language = cfg['language']
|
||||||
if cfg.get("header") is False:
|
if cfg.get('header') is False:
|
||||||
config.header = False
|
config.header = False
|
||||||
if cfg.get("short_feed"):
|
|
||||||
config.short_feed = True
|
|
||||||
|
|
||||||
config.categories = cfg.get("categories", [])
|
config.categories = cfg.get('categories', [])
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -31,11 +31,11 @@ def call(*args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
# Defines our basic inline image
|
# Defines our basic inline image
|
||||||
img_expr = '<img class="latex inline math-%s" id="%s" src="data:image/png;base64,%s">'
|
img_expr = '<img class="latex inline math-%s" alt="%s" id="%s" src="data:image/png;base64,%s">'
|
||||||
|
|
||||||
# Defines multiline expression image
|
# Defines multiline expression image
|
||||||
multiline_img_expr = """<div class="multiline-wrapper">
|
multiline_img_expr = '''<div class="multiline-wrapper">
|
||||||
<img class="latex multiline math-%s" id="%s" src="data:image/png;base64,%s"></div>"""
|
<img class="latex multiline math-%s" alt="%s" id="%s" src="data:image/png;base64,%s"></div>'''
|
||||||
|
|
||||||
# Base CSS template
|
# Base CSS template
|
||||||
img_css = """<style scoped>
|
img_css = """<style scoped>
|
||||||
|
@ -55,8 +55,8 @@ img.latex.inline {
|
||||||
</style>"""
|
</style>"""
|
||||||
|
|
||||||
# Cache and temp file paths
|
# Cache and temp file paths
|
||||||
tmpdir = tempfile.gettempdir() + "/markdown-latex"
|
tmpdir = tempfile.gettempdir() + '/markdown-latex'
|
||||||
cache_file = tmpdir + "/latex.cache"
|
cache_file = tmpdir + '/latex.cache'
|
||||||
|
|
||||||
|
|
||||||
class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
|
@ -75,20 +75,16 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Math TeX extraction regex
|
# Math TeX extraction regex
|
||||||
math_extract_regex = re.compile(
|
math_extract_regex = re.compile(r'(.+?)((\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n))(.*)', re.MULTILINE | re.DOTALL)
|
||||||
r"(.+?)((\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n))(.*)", re.MULTILINE | re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
# Math TeX matching regex
|
# Math TeX matching regex
|
||||||
math_match_regex = re.compile(
|
math_match_regex = re.compile(r'\s*(\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n)\s*', re.MULTILINE | re.DOTALL)
|
||||||
r"\s*(\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n)\s*", re.MULTILINE | re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *_, **__):
|
def __init__(self, *_, **__):
|
||||||
if not os.path.isdir(tmpdir):
|
if not os.path.isdir(tmpdir):
|
||||||
os.makedirs(tmpdir)
|
os.makedirs(tmpdir)
|
||||||
try:
|
try:
|
||||||
with open(cache_file, "r") as f:
|
with open(cache_file, 'r') as f:
|
||||||
self.cached = json.load(f)
|
self.cached = json.load(f)
|
||||||
except (IOError, json.JSONDecodeError):
|
except (IOError, json.JSONDecodeError):
|
||||||
self.cached = {}
|
self.cached = {}
|
||||||
|
@ -98,8 +94,7 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
("dvipng", "args"): "-q -T tight -bg Transparent -z 9 -D 200",
|
("dvipng", "args"): "-q -T tight -bg Transparent -z 9 -D 200",
|
||||||
("delimiters", "text"): "%",
|
("delimiters", "text"): "%",
|
||||||
("delimiters", "math"): "$",
|
("delimiters", "math"): "$",
|
||||||
("delimiters", "preamble"): "%%",
|
("delimiters", "preamble"): "%%"}
|
||||||
}
|
|
||||||
|
|
||||||
def _latex_to_base64(self, tex):
|
def _latex_to_base64(self, tex):
|
||||||
"""Generates a base64 representation of TeX string"""
|
"""Generates a base64 representation of TeX string"""
|
||||||
|
@ -109,24 +104,18 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
with os.fdopen(tmp_file_fd, "w") as tmp_file:
|
with os.fdopen(tmp_file_fd, "w") as tmp_file:
|
||||||
tmp_file.write(self.tex_preamble)
|
tmp_file.write(self.tex_preamble)
|
||||||
tmp_file.write(tex)
|
tmp_file.write(tex)
|
||||||
tmp_file.write("\n\\end{document}")
|
tmp_file.write('\n\\end{document}')
|
||||||
|
|
||||||
# compile LaTeX document. A DVI file is created
|
# compile LaTeX document. A DVI file is created
|
||||||
status = call(
|
status = call(('latex -halt-on-error -output-directory={:s} {:s}'
|
||||||
(
|
.format(tmpdir, path)).split(),
|
||||||
"latex -halt-on-error -output-directory={:s} {:s}".format(tmpdir, path)
|
stdout=PIPE, timeout=10)
|
||||||
).split(),
|
|
||||||
stdout=PIPE,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
# clean up if the above failed
|
# clean up if the above failed
|
||||||
if status:
|
if status:
|
||||||
self._cleanup(path, err=True)
|
self._cleanup(path, err=True)
|
||||||
raise Exception(
|
raise Exception("Couldn't compile LaTeX document." +
|
||||||
"Couldn't compile LaTeX document."
|
"Please read '%s.log' for more detail." % path)
|
||||||
+ "Please read '%s.log' for more detail." % path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run dvipng on the generated DVI file. Use tight bounding box.
|
# Run dvipng on the generated DVI file. Use tight bounding box.
|
||||||
# Magnification is set to 1200
|
# Magnification is set to 1200
|
||||||
|
@ -140,10 +129,8 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
# clean up if we couldn't make the above work
|
# clean up if we couldn't make the above work
|
||||||
if status:
|
if status:
|
||||||
self._cleanup(path, err=True)
|
self._cleanup(path, err=True)
|
||||||
raise Exception(
|
raise Exception("Couldn't convert LaTeX to image." +
|
||||||
"Couldn't convert LaTeX to image."
|
"Please read '%s.log' for more detail." % path)
|
||||||
+ "Please read '%s.log' for more detail." % path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Read the png and encode the data
|
# Read the png and encode the data
|
||||||
try:
|
try:
|
||||||
|
@ -170,7 +157,7 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
def run(self, lines):
|
def run(self, lines):
|
||||||
"""Parses the actual page"""
|
"""Parses the actual page"""
|
||||||
# Checks for the LaTeX header
|
# Checks for the LaTeX header
|
||||||
use_latex = any(line == "[//]: # (latex: 1)" for line in lines)
|
use_latex = any(line == '[//]: # (latex: 1)' for line in lines)
|
||||||
if not use_latex:
|
if not use_latex:
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
@ -178,9 +165,7 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
page = "\n".join(lines)
|
page = "\n".join(lines)
|
||||||
|
|
||||||
# Adds a preamble mode
|
# Adds a preamble mode
|
||||||
self.tex_preamble += (
|
self.tex_preamble += self.config[("general", "preamble")] + "\n\\begin{document}\n"
|
||||||
self.config[("general", "preamble")] + "\n\\begin{document}\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Figure out our text strings and math-mode strings
|
# Figure out our text strings and math-mode strings
|
||||||
tex_expr = self.math_extract_regex.findall(page)
|
tex_expr = self.math_extract_regex.findall(page)
|
||||||
|
@ -191,7 +176,7 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
|
|
||||||
# Parse the expressions
|
# Parse the expressions
|
||||||
new_cache = {}
|
new_cache = {}
|
||||||
new_page = ""
|
new_page = ''
|
||||||
n_multiline_expressions = 0
|
n_multiline_expressions = 0
|
||||||
|
|
||||||
while page:
|
while page:
|
||||||
|
@ -215,25 +200,21 @@ class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
|
||||||
new_cache[tex_hash] = data
|
new_cache[tex_hash] = data
|
||||||
|
|
||||||
if is_multiline and n_multiline_expressions > 0:
|
if is_multiline and n_multiline_expressions > 0:
|
||||||
new_page += "</p>"
|
new_page += '</p>'
|
||||||
new_page += (multiline_img_expr if is_multiline else img_expr) % (
|
new_page += (multiline_img_expr if is_multiline else img_expr) % ('true', expr, tex_hash, data)
|
||||||
"true",
|
|
||||||
tex_hash,
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_multiline:
|
if is_multiline:
|
||||||
new_page += "<p>"
|
new_page += '<p>'
|
||||||
n_multiline_expressions += 1
|
n_multiline_expressions += 1
|
||||||
|
|
||||||
page = m.group(5)
|
page = m.group(5)
|
||||||
|
|
||||||
if n_multiline_expressions > 0:
|
if n_multiline_expressions > 0:
|
||||||
new_page += "</p>"
|
new_page += '</p>'
|
||||||
|
|
||||||
# Cache our data
|
# Cache our data
|
||||||
self.cached.update(new_cache)
|
self.cached.update(new_cache)
|
||||||
with open(cache_file, "w") as f:
|
with open(cache_file, 'w') as f:
|
||||||
json.dump(self.cached, f)
|
json.dump(self.cached, f)
|
||||||
|
|
||||||
# Make sure to re-split the lines
|
# Make sure to re-split the lines
|
||||||
|
@ -249,7 +230,7 @@ class MarkdownLatex(markdown.Extension):
|
||||||
|
|
||||||
def extendMarkdown(self, md):
|
def extendMarkdown(self, md):
|
||||||
md.preprocessors.register(
|
md.preprocessors.register(
|
||||||
LaTeXPreprocessor(self),
|
LaTeXPreprocessor(self),
|
||||||
"latex",
|
'latex',
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,131 +1,84 @@
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
from flask import (
|
from flask import request, Response, send_from_directory as send_from_directory_, render_template
|
||||||
jsonify,
|
|
||||||
request,
|
|
||||||
Response,
|
|
||||||
send_from_directory as send_from_directory_,
|
|
||||||
render_template,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .app import app
|
from .app import app
|
||||||
from .config import config
|
from .config import config
|
||||||
from ._sorters import PagesSortByTimeGroupedByFolder
|
from ._sorters import PagesSortByTimeGroupedByFolder
|
||||||
|
|
||||||
|
|
||||||
def send_from_directory(
|
def send_from_directory(path: str, file: str, alternative_path: Optional[str] = None, *args, **kwargs):
|
||||||
path: str, file: str, alternative_path: Optional[str] = None, *args, **kwargs
|
|
||||||
):
|
|
||||||
if not os.path.exists(os.path.join(path, file)) and alternative_path:
|
if not os.path.exists(os.path.join(path, file)) and alternative_path:
|
||||||
path = alternative_path
|
path = alternative_path
|
||||||
return send_from_directory_(path, file, *args, **kwargs)
|
return send_from_directory_(path, file, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["GET"])
|
@app.route('/', methods=['GET'])
|
||||||
def home_route():
|
def home_route():
|
||||||
return render_template(
|
return render_template(
|
||||||
"index.html",
|
'index.html',
|
||||||
pages=app.get_pages(sorter=PagesSortByTimeGroupedByFolder),
|
pages=app.get_pages(sorter=PagesSortByTimeGroupedByFolder),
|
||||||
config=config,
|
config=config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/img/<img>", methods=["GET"])
|
@app.route('/img/<img>', methods=['GET'])
|
||||||
def img_route(img: str):
|
def img_route(img: str):
|
||||||
return send_from_directory(app.img_dir, img, config.default_img_dir)
|
return send_from_directory(app.img_dir, img, config.default_img_dir)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/favicon.ico", methods=["GET"])
|
@app.route('/favicon.ico', methods=['GET'])
|
||||||
def favicon_route():
|
def favicon_route():
|
||||||
return img_route("favicon.ico")
|
return img_route('favicon.ico')
|
||||||
|
|
||||||
|
|
||||||
@app.route("/js/<file>", methods=["GET"])
|
@app.route('/js/<file>', methods=['GET'])
|
||||||
def js_route(file: str):
|
def js_route(file: str):
|
||||||
return send_from_directory(app.js_dir, file, config.default_js_dir)
|
return send_from_directory(app.js_dir, file, config.default_js_dir)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/pwabuilder-sw.js", methods=["GET"])
|
@app.route('/pwabuilder-sw.js', methods=['GET'])
|
||||||
def pwa_builder_route():
|
def pwa_builder_route():
|
||||||
return send_from_directory(app.js_dir, "pwabuilder-sw.js", config.default_js_dir)
|
return send_from_directory(app.js_dir, 'pwabuilder-sw.js', config.default_js_dir)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/pwabuilder-sw-register.js", methods=["GET"])
|
@app.route('/pwabuilder-sw-register.js', methods=['GET'])
|
||||||
def pwa_builder_register_route():
|
def pwa_builder_register_route():
|
||||||
return send_from_directory(
|
return send_from_directory(app.js_dir, 'pwabuilder-sw-register.js', config.default_js_dir)
|
||||||
app.js_dir, "pwabuilder-sw-register.js", config.default_js_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/css/<style>", methods=["GET"])
|
@app.route('/css/<style>', methods=['GET'])
|
||||||
def css_route(style: str):
|
def css_route(style: str):
|
||||||
return send_from_directory(app.css_dir, style, config.default_css_dir)
|
return send_from_directory(app.css_dir, style, config.default_css_dir)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/fonts/<file>", methods=["GET"])
|
@app.route('/fonts/<file>', methods=['GET'])
|
||||||
def fonts_route(file: str):
|
def fonts_route(file: str):
|
||||||
return send_from_directory(app.fonts_dir, file, config.default_fonts_dir)
|
return send_from_directory(app.fonts_dir, file, config.default_fonts_dir)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/manifest.json", methods=["GET"])
|
@app.route('/manifest.json', methods=['GET'])
|
||||||
def manifest_route():
|
def manifest_route():
|
||||||
# If there is a manifest.json in the content directory, use it
|
return send_from_directory(config.content_dir, 'manifest.json')
|
||||||
manifest_file = os.path.join(config.content_dir, "manifest.json")
|
|
||||||
if os.path.isfile(manifest_file):
|
|
||||||
return send_from_directory(config.content_dir, "manifest.json")
|
|
||||||
|
|
||||||
# Otherwise, generate a default manifest.json
|
|
||||||
return jsonify(
|
|
||||||
{
|
|
||||||
"name": config.title,
|
|
||||||
"short_name": config.title,
|
|
||||||
"icons": [
|
|
||||||
{"src": "/img/icon-48.png", "sizes": "48x48", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-72.png", "sizes": "72x72", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-96.png", "sizes": "96x96", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-144.png", "sizes": "144x144", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-168.png", "sizes": "168x168", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-192.png", "sizes": "192x192", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-256.png", "sizes": "256x256", "type": "image/png"},
|
|
||||||
{"src": "/img/icon-512.png", "sizes": "512x512", "type": "image/png"},
|
|
||||||
],
|
|
||||||
"gcm_sender_id": "",
|
|
||||||
"gcm_user_visible_only": True,
|
|
||||||
"start_url": "/",
|
|
||||||
"permissions": ["gcm"],
|
|
||||||
"scope": "",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/article/<path:path>/<article>", methods=["GET"])
|
@app.route('/article/<path:path>/<article>', methods=['GET'])
|
||||||
def article_with_path_route(path: str, article: str):
|
def article_with_path_route(path: str, article: str):
|
||||||
return app.get_page(os.path.join(path, article))
|
return app.get_page(os.path.join(path, article))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/article/<article>", methods=["GET"])
|
@app.route('/article/<article>', methods=['GET'])
|
||||||
def article_route(article: str):
|
def article_route(article: str):
|
||||||
return article_with_path_route("", article)
|
return article_with_path_route('', article)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/rss", methods=["GET"])
|
@app.route('/rss', methods=['GET'])
|
||||||
def rss_route():
|
def rss_route():
|
||||||
short_description = "short" in request.args or config.short_feed
|
pages = app.get_pages(with_content=True, skip_header=True, skip_html_head=True)
|
||||||
pages = app.get_pages(
|
short_description = 'short' in request.args
|
||||||
with_content=not short_description,
|
|
||||||
skip_header=True,
|
|
||||||
skip_html_head=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(
|
return Response('''<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
"""<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
|
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
|
||||||
<channel>
|
<channel>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
|
@ -142,56 +95,36 @@ def rss_route():
|
||||||
|
|
||||||
{items}
|
{items}
|
||||||
</channel>
|
</channel>
|
||||||
</rss>""".format(
|
</rss>'''.format(
|
||||||
title=config.title,
|
title=config.title,
|
||||||
description=config.description,
|
description=config.description,
|
||||||
link=config.link,
|
link=config.link,
|
||||||
categories=",".join(config.categories),
|
categories=','.join(config.categories),
|
||||||
language=config.language,
|
language=config.language,
|
||||||
last_pub_date=(
|
last_pub_date=(
|
||||||
pages[0][1]["published"].strftime("%a, %d %b %Y %H:%M:%S GMT")
|
pages[0][1]['published'].strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||||
if pages
|
if pages else ''
|
||||||
else ""
|
),
|
||||||
),
|
items='\n\n'.join([
|
||||||
items="\n\n".join(
|
'''
|
||||||
[
|
|
||||||
(
|
|
||||||
"""
|
|
||||||
<item>
|
<item>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link>{base_link}{link}</link>
|
<link>{base_link}{link}</link>
|
||||||
<pubDate>{published}</pubDate>
|
<pubDate>{published}</pubDate>
|
||||||
<description><![CDATA[{content}]]></description>
|
<description><![CDATA[{content}]]></description>
|
||||||
<media:content medium="image" url="{image}" width="200" height="150" />
|
<media:content medium="image" url="{base_link}{image}" width="200" height="150" />
|
||||||
</item>
|
</item>
|
||||||
"""
|
'''.format(
|
||||||
).format(
|
base_link=config.link,
|
||||||
base_link=config.link,
|
title=page.get('title', '[No Title]'),
|
||||||
title=page.get("title", "[No Title]"),
|
link=page.get('uri', ''),
|
||||||
link=page.get("uri", ""),
|
published=page['published'].strftime('%a, %d %b %Y %H:%M:%S GMT') if 'published' in page else '',
|
||||||
published=(
|
content=page.get('description', '') if short_description else page.get('content', ''),
|
||||||
page["published"].strftime("%a, %d %b %Y %H:%M:%S GMT")
|
image=page.get('image', ''),
|
||||||
if "published" in page
|
)
|
||||||
else ""
|
for _, page in pages
|
||||||
),
|
]),
|
||||||
content=(
|
), mimetype='application/rss+xml')
|
||||||
page.get("description", "")
|
|
||||||
if short_description
|
|
||||||
else page.get("content", "")
|
|
||||||
),
|
|
||||||
image=(
|
|
||||||
urljoin(config.link, page["image"])
|
|
||||||
if page.get("image")
|
|
||||||
and not re.search(r"^https?://", page["image"])
|
|
||||||
else page.get("image", "")
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for _, page in pages
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
mimetype="application/xml",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -2,31 +2,22 @@ main .content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main .content p,
|
main .content p,
|
||||||
main .content ul,
|
main .content ul {
|
||||||
main .content ol {
|
|
||||||
font-family: Lora, "Palatino Linotype", "Book Antiqua", "New York", "DejaVu serif", serif;
|
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main .content code, .codehilite {
|
main .content code, .codehilite {
|
||||||
font-size: 0.9em;
|
font-size: .85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
main {
|
main {
|
||||||
font-size: 0.95em;
|
font-size: 0.9em;
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
main p {
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +45,13 @@ a:hover {
|
||||||
|
|
||||||
@media screen and (min-width: 1024px) {
|
@media screen and (min-width: 1024px) {
|
||||||
main .container {
|
main .container {
|
||||||
max-width: 40em;
|
max-width: 35em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.codehilite {
|
.codehilite {
|
||||||
padding: 0 .5em;
|
padding: 0 .5em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-image-container {
|
.main-image-container {
|
||||||
|
|
|
@ -2,8 +2,7 @@ html {
|
||||||
height: -webkit-fill-available;
|
height: -webkit-fill-available;
|
||||||
height: -moz-available;
|
height: -moz-available;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
/* font-family: Lora, "Palatino Linotype", "Book Antiqua", "New York", "DejaVu serif", serif; */
|
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +95,18 @@ main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 0.25em;
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
main {
|
||||||
|
padding: 0 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
main {
|
||||||
|
padding: 0 2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -109,10 +119,6 @@ h2 {
|
||||||
line-height: 1.1em;
|
line-height: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: .65em;
|
font-size: .65em;
|
||||||
|
@ -123,7 +129,3 @@ footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 1px -2px 2px 0 #bbb;
|
box-shadow: 1px -2px 2px 0 #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index {
|
|
||||||
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,36 +0,0 @@
|
||||||
/* lora-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lora';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url('/fonts/Lora-Regular.eot'); /* IE9 Compat Modes */
|
|
||||||
src: local('Lora'), local('Lora-Regular'),
|
|
||||||
url('/fonts/Lora-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
|
||||||
url('/fonts/Lora-Regular.woff2') format('woff2'), /* Super Modern Browsers */
|
|
||||||
url('/fonts/Lora-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('/fonts/Lora-Regular.ttf') format('truetype'); /* Safari, Android, iOS */
|
|
||||||
}
|
|
||||||
/* lora-700 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lora';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: url('/fonts/Lora-Bold.eot'); /* IE9 Compat Modes */
|
|
||||||
src: local('Lora Bold'), local('Lora-Bold'),
|
|
||||||
url('/fonts/Lora-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
|
||||||
url('/fonts/Lora-Bold.woff2') format('woff2'), /* Super Modern Browsers */
|
|
||||||
url('/fonts/Lora-Bold.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('/fonts/Lora-Bold.ttf') format('truetype'); /* Safari, Android, iOS */
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lora';
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url('/fonts/Lora-Italic.eot'); /* IE9 Compat Modes */
|
|
||||||
src: local('Lora Italic'), local('Lora-Italic'),
|
|
||||||
url('/fonts/Lora-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
|
||||||
url('/fonts/Lora-Italic.woff2') format('woff2'), /* Super Modern Browsers */
|
|
||||||
url('/fonts/Lora-Italic.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('/fonts/Lora-Italic.ttf') format('truetype'); /* Safari, Android, iOS */
|
|
||||||
}
|
|
||||||
|
|
Binary file not shown.
Before ![]() (image error) Size: 1.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 1.4 KiB |
Binary file not shown.
Before ![]() (image error) Size: 1.7 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.1 KiB |
Binary file not shown.
Before ![]() (image error) Size: 774 B |
Binary file not shown.
Before ![]() (image error) Size: 5.8 KiB |
Binary file not shown.
Before ![]() (image error) Size: 840 B |
Binary file not shown.
Before ![]() (image error) Size: 1 KiB |
|
@ -1 +0,0 @@
|
||||||
icon-512.png
|
|
Before ![]() (image error) Size: 12 B After ![]() (image error) Size: 5.8 KiB ![]() ![]() |
BIN
madblog/static/img/icon.png
Normal file
BIN
madblog/static/img/icon.png
Normal file
Binary file not shown.
Before ![]() (image error) Size: 12 B After ![]() (image error) Size: 5.8 KiB ![]() ![]() |
|
@ -2,30 +2,12 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
|
|
||||||
<!-- PWA & Viewport -->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
<meta name="description" content="{{ config.description }}">
|
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
<!-- Android PWA -->
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="application-name" content="{{ config.title }}">
|
|
||||||
<!-- iOS PWA -->
|
|
||||||
<meta name="apple-mobile-web-app-title" content="{{ config.title }}">
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
||||||
<!-- Orientation -->
|
|
||||||
<meta name="screen-orientation" content="portrait">
|
|
||||||
<!-- RSS feed -->
|
|
||||||
<link rel="alternate" type="application/rss+xml" title="{{ config.title }}" href="/rss" />
|
<link rel="alternate" type="application/rss+xml" title="{{ config.title }}" href="/rss" />
|
||||||
<!-- Fonts & Styles -->
|
|
||||||
<link rel="stylesheet" href="/fonts/lora.css">
|
|
||||||
<link rel="stylesheet" href="/fonts/poppins.css">
|
<link rel="stylesheet" href="/fonts/poppins.css">
|
||||||
<link rel="stylesheet" href="/fonts/fira-sans.css">
|
<link rel="stylesheet" href="/fonts/fira-sans.css">
|
||||||
<link rel="stylesheet" href="/css/common.css">
|
<link rel="stylesheet" href="/css/common.css">
|
||||||
<!-- PWA builder -->
|
<link rel="manifest" href="/manifest.json">
|
||||||
<script type="module" src="/pwabuilder-sw-register.js"></script>
|
<script type="module" src="/pwabuilder-sw-register.js"></script>
|
||||||
|
|
||||||
{% if styles %}
|
{% if styles %}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
[project]
|
|
||||||
name = "madblog"
|
|
||||||
description = "A general-purpose framework for automation"
|
|
||||||
dynamic = ["version", "dependencies", "optional-dependencies", "entry-points", "license"]
|
|
||||||
authors = [
|
|
||||||
{name = "Fabio Manganiello", email = "fabio@manganiello.tech"},
|
|
||||||
]
|
|
||||||
|
|
||||||
classifiers=[
|
|
||||||
"Topic :: Utilities",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Development Status :: 4 - Beta",
|
|
||||||
]
|
|
||||||
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = '>= 3.8'
|
|
||||||
keywords = ["blog", "markdown"]
|
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
|
||||||
version = {attr = "madblog.__version__"}
|
|
||||||
dependencies = {file = "requirements.txt"}
|
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
madblog = 'madblog:__main__'
|
|
||||||
|
|
||||||
[tool.bumpversion]
|
|
||||||
current_version = "0.2.35"
|
|
||||||
commit = true
|
|
||||||
tag = true
|
|
||||||
|
|
||||||
[[tool.bumpversion.files]]
|
|
||||||
filename = "madblog/__init__.py"
|
|
||||||
|
|
||||||
[[tool.bumpversion.files]]
|
|
||||||
filename = "setup.py"
|
|
7
setup.cfg
Normal file
7
setup.cfg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[bumpversion]
|
||||||
|
current_version = 0.2.12
|
||||||
|
commit = True
|
||||||
|
tag = True
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
description-file = README.md
|
39
setup.py
39
setup.py
|
@ -1,41 +1,42 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
def readfile(file):
|
def readfile(file):
|
||||||
with open(file, "r") as f:
|
with open(file, 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="madblog",
|
name='madblog',
|
||||||
version="0.2.35",
|
version='0.2.12',
|
||||||
author="Fabio Manganiello",
|
author='Fabio Manganiello',
|
||||||
author_email="info@fabiomanganiello.com",
|
author_email='info@fabiomanganiello.com',
|
||||||
description="A minimal platform for Markdown-based blogs",
|
description='A minimal platform for Markdown-based blogs',
|
||||||
license="MIT",
|
license='MIT',
|
||||||
python_requires=">= 3.8",
|
python_requires='>= 3.8',
|
||||||
keywords="blog markdown",
|
keywords='blog markdown',
|
||||||
url="https://git.platypush.tech/blacklight/madblog",
|
url='https://git.platypush.tech/blacklight/madblog',
|
||||||
packages=find_packages(include=["madblog"]),
|
packages=find_packages(include=['madblog']),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
'console_scripts': [
|
||||||
"madblog=madblog.cli:run",
|
'madblog=madblog.cli:run',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
long_description=readfile("README.md"),
|
long_description=readfile('README.md'),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type='text/markdown',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Topic :: Utilities",
|
"Topic :: Utilities",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"flask",
|
'flask',
|
||||||
"markdown",
|
'markdown',
|
||||||
"pygments",
|
'pygments',
|
||||||
"pyyaml",
|
'pyyaml',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue