21 Commits

Author SHA1 Message Date
15142054da tweak the appearance of footnotes 2021-02-12 19:37:25 -06:00
dc81ef35de float image left/right for inlining in an article 2021-02-12 12:51:43 -06:00
c292f33334 CSS for framing an image inline the article 2021-02-12 12:51:12 -06:00
1c052b8409 pin bandit in requirements-dev since 1.7.0 is weird in tox 2021-02-12 09:28:12 -06:00
7cf8a427ce add an .img-25 for 25% wide images 2021-02-12 09:26:10 -06:00
e8a749d9ba Revert "tweak the base text line height, again"
This reverts commit 1878d5951b.

the more I look at this, the more I like the old text spacing
2021-02-12 09:21:21 -06:00
ae72fe87b5 class to center an image as a block element
this is effectively a replacement for div.splash means of getting a
centered header image, and can be used anywhere
2021-02-12 09:19:35 -06:00
bb0e71e9e4 give *all* images max-width of the inner column
this was done for the giant splash logo but I should really just
restrain this everywhere
2021-02-12 09:18:54 -06:00
3bfdacdb6d add attr_list to markdown extensions
this will lead to me putting less HTML in the .md files, which is a good
thing
2021-02-12 09:15:41 -06:00
e6d2015de5 use smarty markdown extension for dashes, ellipses 2021-02-11 19:05:01 -06:00
56eb767e33 don't let sub/superscripts affect line height 2021-02-11 18:53:57 -06:00
07031fe667 enable footnotes extra for markdown 2021-02-11 18:36:48 -06:00
48c6e8495a provide some styling of footnotes 2021-02-11 18:20:42 -06:00
4f45943775 initialize markdown on a per-page basis
the footnote extra expects to only parse one document over the Markup's
lifetime, and writes the footnotes to the bottom of every page that is
rendered (again assuming only one) with links back to the reference

having one parser for the entire app, naturally, introduced
ever-increasing footnote links and every footnote on the site showing up
on every page. this was not intended

in some light testing, doing this per-request has a nominal effect on
performance
2021-02-11 18:17:26 -06:00
b26ea6a661 add html tag in order to specify lang="en" 2021-02-11 09:36:24 -06:00
1878d5951b tweak the base text line height, again 2021-02-11 09:35:21 -06:00
829165ad8c style link underline same color as the hover 2021-02-11 09:35:21 -06:00
7d982b96c9 tweak text colors; less normal, more bold 2021-02-11 09:35:21 -06:00
5e41cde52e use a flexbox for the header sections
this is better than a float because I have always kind of hated how
floating divs work, and this also orders and displays the navs better in
elinks
2021-02-11 00:23:19 -06:00
ad33cf2e83 replace section tags with div tags
syntactically incorrect usage, as picked up by a W3C validator
2021-02-11 00:08:19 -06:00
87ad48d8d2 add mdx-linkify to markdown extensions 2021-01-22 09:51:53 -06:00
13 changed files with 335 additions and 118 deletions

View File

@@ -3,7 +3,6 @@ import logging
import os import os
from logging.config import dictConfig from logging.config import dictConfig
import markdown
from flask import Flask, request, send_from_directory from flask import Flask, request, send_from_directory
from ._version import get_versions from ._version import get_versions
@@ -32,10 +31,6 @@ def create_app(instance_path=None, test_config=None):
logger.debug("instance path: %s", app.instance_path) logger.debug("instance path: %s", app.instance_path)
# initialize markdown parser from config, but include
# extensions our app depends on, like the meta extension
app.config['md'] = markdown.Markdown(extensions=app.config['MARKDOWN_EXTENSIONS'] + ['meta'])
@app.before_request @app.before_request
def log_request(): def log_request():
logger.info("REQUEST: %s %s", request.method, request.path) logger.info("REQUEST: %s %s", request.method, request.path)

View File

@@ -32,7 +32,21 @@ class Config(object):
}, },
} }
MARKDOWN_EXTENSIONS = ['meta', 'tables'] MARKDOWN_EXTENSIONS = ['extra', 'mdx_linkify', 'smarty', 'tables']
MARKDOWN_EXTENSION_CONFIGS = {
'extra': {
'attr_list': {},
'footnotes': {
'UNIQUE_IDS': True,
},
},
'smarty': {
'smart_dashes': True,
'smart_quotes': False,
'smart_angled_quotes': False,
'smart_ellipses': True,
},
}
DEFAULT_PAGE_STYLE = 'light' DEFAULT_PAGE_STYLE = 'light'

View File

@@ -1,7 +1,21 @@
"""Miscellaneous helper functions and whatnot.""" """Miscellaneous helper functions and whatnot."""
import markdown
from flask import current_app as app from flask import current_app as app
def get_meta_str(key): def get_meta_str(md, key):
"""Provide the page's metadata for the specified key, or '' if unset.""" """Provide the page's (parsed in Markup obj md) metadata for the specified key, or '' if unset."""
return " ".join(app.config['md'].Meta.get(key)) if app.config['md'].Meta.get(key) else "" return " ".join(md.Meta.get(key)) if md.Meta.get(key) else ""
def init_md():
"""Initialize the Markdown parser.
This used to done at the app level in __init__, but extensions like footnotes apparently
assume the parser to only live for the length of parsing one document, and create double
footnote ref links if the one parser sees the same document multiple times.
"""
# initialize markdown parser from config, but include
# extensions our app depends on, like the meta extension
return markdown.Markdown(extensions=app.config['MARKDOWN_EXTENSIONS'] + ['meta'],
extension_configs=app.config['MARKDOWN_EXTENSION_CONFIGS'])

View File

@@ -8,7 +8,7 @@ from flask import current_app as app
from flask import make_response, redirect, render_template, request from flask import make_response, redirect, render_template, request
from tzlocal import get_localzone from tzlocal import get_localzone
from incorporealcms.lib import get_meta_str from incorporealcms.lib import get_meta_str, init_md
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -34,11 +34,12 @@ def display_page(path):
logger.warning("requested path '%s' (resolved path '%s') not found!", path, resolved_path) logger.warning("requested path '%s' (resolved path '%s') not found!", path, resolved_path)
abort(404) abort(404)
else: else:
content = Markup(app.config['md'].convert(entry)) md = init_md()
logger.debug("file metadata: %s", app.config['md'].Meta) content = Markup(md.convert(entry))
logger.debug("file metadata: %s", md.Meta)
return render('base.html', title=get_meta_str('title'), description=get_meta_str('description'), return render('base.html', title=get_meta_str(md, 'title'), description=get_meta_str(md, 'description'),
image=get_meta_str('image'), base_url=request.base_url, content=content, navs=parent_navs, image=get_meta_str(md, 'image'), base_url=request.base_url, content=content, navs=parent_navs,
mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z')) mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z'))
@@ -111,6 +112,8 @@ def generate_parent_navs(path):
else: else:
with app.open_instance_resource(resolved_path, 'r') as entry_file: with app.open_instance_resource(resolved_path, 'r') as entry_file:
entry = entry_file.read() entry = entry_file.read()
_ = Markup(app.config['md'].convert(entry)) # for issues regarding parser reuse (see lib.init_md) we reinitialize the parser here
page_name = " ".join(app.config['md'].Meta.get('title')) if app.config['md'].Meta.get('title') else f'/{path}' md = init_md()
_ = Markup(md.convert(entry))
page_name = " ".join(md.Meta.get('title')) if md.Meta.get('title') else f'/{path}'
return generate_parent_navs(parent_path) + [(page_name, f'/{path}')] return generate_parent_navs(parent_path) + [(page_name, f'/{path}')]

View File

@@ -1,11 +1,15 @@
html { html {
color: #DDD; color: #CCC;
} }
body { body {
background: black; background: black;
} }
strong {
color: #EEE;
}
.site-wrap { .site-wrap {
background: #111; background: #111;
@@ -25,16 +29,19 @@ a:link, a:visited {
a:hover, a:active { a:hover, a:active {
color: #B31D15; color: #B31D15;
border-bottom: 1px dotted #EEE; border-bottom: 1px dotted #B31D15;
} }
section.nav, section.styles { div.header {
color: #BBB;
background: #222; background: #222;
border-bottom: 1px solid #222; border-bottom: 1px solid #222;
} }
section.nav a, section.styles a { div.nav, div.styles {
color: #BBB;
}
div.nav a, div.styles a {
color: #BBB; color: #BBB;
} }
@@ -51,6 +58,11 @@ blockquote {
border: 1px solid #222; border: 1px solid #222;
} }
.img-frame {
background-color: rgba(255, 255, 255, 0.1);
border: 1px solid #333;
}
figure { figure {
background: #222; background: #222;
border: 1px solid #333; border: 1px solid #333;

View File

@@ -3,7 +3,11 @@ html {
} }
body { body {
background: #888; background: #999;
}
strong {
color: #111;
} }
.site-wrap { .site-wrap {
@@ -25,16 +29,19 @@ a:link, a:visited {
a:hover, a:active { a:hover, a:active {
color: #811610; color: #811610;
border-bottom: 1px dotted #111; border-bottom: 1px dotted #811610;
} }
section.nav, section.styles { div.header {
color: #666;
background: #EEE; background: #EEE;
border-bottom: 1px solid #CCC; border-bottom: 1px solid #CCC;
} }
section.nav a, section.styles a { div.nav, div.styles {
color: #666;
}
div.nav a, div.styles a {
color: #666; color: #666;
} }
@@ -51,6 +58,11 @@ blockquote {
border: 1px solid #CCC; border: 1px solid #CCC;
} }
.img-frame {
background-color: rgba(0, 0, 0, 0.1);
border: 1px solid #BBB;
}
figure { figure {
background: #EFEFEF; background: #EFEFEF;
border: 1px solid #CCCCCC; border: 1px solid #CCCCCC;

View File

@@ -63,27 +63,36 @@ a:active {
text-decoration: none; text-decoration: none;
} }
section.nav, section.styles { div.header {
display: flex;
justify-content: space-between;
}
div.nav, div.styles {
font-size: 0.75em; font-size: 0.75em;
border-bottom: 1px solid #444;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
} }
section.nav a, section.styles a { div.nav a, div.styles a {
border-bottom: none; border-bottom: none;
} }
section.styles { div.content {
float: right;
}
section.content {
font-size: 11pt; font-size: 11pt;
padding: 0 1em; padding: 0 1em;
line-height: 1.5em; line-height: 1.5em;
} }
sup, sub {
vertical-align: baseline;
position: relative;
top: -0.4em;
}
sub {
top: 0.4em;
}
footer { footer {
display: block; display: block;
font-size: 75%; font-size: 75%;
@@ -114,14 +123,38 @@ blockquote {
text-align: center; text-align: center;
} }
.splash img { img {
max-width: 100%; max-width: 100%;
} }
.img-25 {
max-width: 25% !important;
}
.img-50 { .img-50 {
max-width: 50% !important; max-width: 50% !important;
} }
.img-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.img-left {
float: left;
margin-right: 1em;
}
.img-right {
float: right;
margin-left: 1em;
}
.img-frame {
padding: 5px;
}
/* For screens with width smaller than 400px */ /* For screens with width smaller than 400px */
.figure-left .figure-right { .figure-left .figure-right {
max-width: 95%; max-width: 95%;
@@ -173,3 +206,16 @@ figcaption {
margin: 5px; margin: 5px;
display: inline; display: inline;
} }
.footnote {
font-size: 0.8em;
}
.footnote p {
margin: 0;
}
.footnote-ref:link, .footnote-ref:visited, .footnote-ref:hover, .footnote-ref:active {
border-bottom: none;
font-weight: normal;
}

View File

@@ -1,4 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en">
<title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title> <title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title>
{% if title %}<meta property="og:title" content="{{ title }}">{% endif %} {% if title %}<meta property="og:title" content="{{ title }}">{% endif %}
{% if description %}<meta property="og:description" content="{{ description }}">{% endif %} {% if description %}<meta property="og:description" content="{{ description }}">{% endif %}
@@ -10,21 +11,24 @@
<link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}"> <link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}">
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}"> <link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}">
<section class="site-wrap"> <div class="site-wrap">
<section class="styles"> <div class="header">
<a href="?style=dark">[dark]</a> <div class="nav">
<a href="?style=light">[light]</a>
</section>
<section class="nav">
{% for nav in navs %} {% for nav in navs %}
<a href="{{ nav.1 }}">{{ nav.0 }}</a> <a href="{{ nav.1 }}">{{ nav.0 }}</a>
{% if not loop.last %} &raquo; {% endif %} {% if not loop.last %} &raquo; {% endif %}
{% endfor %} {% endfor %}
</section> </div>
<section class="content"> <div class="styles">
<a href="?style=dark">[dark]</a>
<a href="?style=light">[light]</a>
</div>
</div>
<div class="content">
{{ content }} {{ content }}
</section> </div>
<footer> <footer>
<i>Last modified: {{ mtime }}</i> <i>Last modified: {{ mtime }}</i>
</footer> </footer>
</section> </div>
</html>

View File

@@ -5,7 +5,7 @@ pytest
pytest-cov pytest-cov
# linting and other static code analysis # linting and other static code analysis
bandit bandit==1.6.2 # pinned because 1.7.0 wasn't running right in tox
dlint dlint
flake8 flake8
flake8-blind-except flake8-blind-except

View File

@@ -4,59 +4,148 @@
# #
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in # pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
# #
appdirs==1.4.4 # via virtualenv appdirs==1.4.4
attrs==20.3.0 # via pytest # via virtualenv
bandit==1.6.3 # via -r requirements/requirements-dev.in attrs==20.3.0
click==7.1.2 # via flask, pip-tools # via pytest
coverage==5.3 # via pytest-cov bandit==1.6.2
distlib==0.3.1 # via virtualenv # via -r requirements/requirements-dev.in
dlint==0.11.0 # via -r requirements/requirements-dev.in bleach==3.2.2
filelock==3.0.12 # via tox, virtualenv # via mdx-linkify
flake8-blind-except==0.1.1 # via -r requirements/requirements-dev.in click==7.1.2
flake8-builtins==1.5.3 # via -r requirements/requirements-dev.in # via
flake8-docstrings==1.5.0 # via -r requirements/requirements-dev.in # flask
flake8-executable==2.1.0 # via -r requirements/requirements-dev.in # pip-tools
flake8-fixme==1.1.1 # via -r requirements/requirements-dev.in coverage==5.3.1
flake8-isort==4.0.0 # via -r requirements/requirements-dev.in # via pytest-cov
flake8-logging-format==0.6.0 # via -r requirements/requirements-dev.in distlib==0.3.1
flake8-mutable==1.2.0 # via -r requirements/requirements-dev.in # via virtualenv
flake8==3.8.4 # via -r requirements/requirements-dev.in, dlint, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort, flake8-mutable dlint==0.11.0
flask==1.1.2 # via -r requirements/requirements.in # via -r requirements/requirements-dev.in
gitdb==4.0.5 # via gitpython filelock==3.0.12
gitpython==3.1.11 # via bandit # via
iniconfig==1.1.1 # via pytest # tox
isort==5.6.4 # via flake8-isort # virtualenv
itsdangerous==1.1.0 # via flask flake8-blind-except==0.2.0
jinja2==2.11.2 # via flask # via -r requirements/requirements-dev.in
markdown==3.3.3 # via -r requirements/requirements.in flake8-builtins==1.5.3
markupsafe==1.1.1 # via jinja2 # via -r requirements/requirements-dev.in
mccabe==0.6.1 # via flake8 flake8-docstrings==1.5.0
packaging==20.7 # via pytest, tox # via -r requirements/requirements-dev.in
pbr==5.5.1 # via stevedore flake8-executable==2.1.1
pip-tools==5.4.0 # via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
pluggy==0.13.1 # via pytest, tox flake8-fixme==1.1.1
py==1.9.0 # via pytest, tox # via -r requirements/requirements-dev.in
pycodestyle==2.6.0 # via flake8 flake8-isort==4.0.0
pydocstyle==5.1.1 # via flake8-docstrings # via -r requirements/requirements-dev.in
pyflakes==2.2.0 # via flake8 flake8-logging-format==0.6.0
pyparsing==2.4.7 # via packaging # via -r requirements/requirements-dev.in
pytest-cov==2.10.1 # via -r requirements/requirements-dev.in flake8-mutable==1.2.0
pytest==6.1.2 # via -r requirements/requirements-dev.in, pytest-cov # via -r requirements/requirements-dev.in
pytz==2020.4 # via tzlocal flake8==3.8.4
pyyaml==5.3.1 # via bandit # via
six==1.15.0 # via bandit, pip-tools, tox, virtualenv # -r requirements/requirements-dev.in
smmap==3.0.4 # via gitdb # dlint
snowballstemmer==2.0.0 # via pydocstyle # flake8-builtins
stevedore==3.3.0 # via bandit # flake8-docstrings
testfixtures==6.15.0 # via flake8-isort # flake8-executable
toml==0.10.2 # via pytest, tox # flake8-isort
tox-wheel==0.6.0 # via -r requirements/requirements-dev.in # flake8-mutable
tox==3.20.1 # via -r requirements/requirements-dev.in, tox-wheel flask==1.1.2
tzlocal==2.1 # via -r requirements/requirements.in # via -r requirements/requirements.in
versioneer==0.19 # via -r requirements/requirements-dev.in gitdb==4.0.5
virtualenv==20.2.2 # via tox # via gitpython
werkzeug==1.0.1 # via flask gitpython==3.1.12
wheel==0.36.1 # via tox-wheel # via bandit
iniconfig==1.1.1
# via pytest
isort==5.7.0
# via flake8-isort
itsdangerous==1.1.0
# via flask
jinja2==2.11.2
# via flask
markdown==3.3.3
# via
# -r requirements/requirements.in
# mdx-linkify
markupsafe==1.1.1
# via jinja2
mccabe==0.6.1
# via flake8
mdx-linkify==2.1
# via -r requirements/requirements.in
packaging==20.8
# via
# bleach
# pytest
# tox
pbr==5.5.1
# via stevedore
pip-tools==5.5.0
# via -r requirements/requirements-dev.in
pluggy==0.13.1
# via
# pytest
# tox
py==1.10.0
# via
# pytest
# tox
pycodestyle==2.6.0
# via flake8
pydocstyle==5.1.1
# via flake8-docstrings
pyflakes==2.2.0
# via flake8
pyparsing==2.4.7
# via packaging
pytest-cov==2.11.1
# via -r requirements/requirements-dev.in
pytest==6.2.1
# via
# -r requirements/requirements-dev.in
# pytest-cov
pytz==2020.5
# via tzlocal
pyyaml==5.4.1
# via bandit
six==1.15.0
# via
# bandit
# bleach
# tox
# virtualenv
smmap==3.0.4
# via gitdb
snowballstemmer==2.1.0
# via pydocstyle
stevedore==3.3.0
# via bandit
testfixtures==6.17.1
# via flake8-isort
toml==0.10.2
# via
# pytest
# tox
tox-wheel==0.6.0
# via -r requirements/requirements-dev.in
tox==3.21.2
# via
# -r requirements/requirements-dev.in
# tox-wheel
tzlocal==2.1
# via -r requirements/requirements.in
versioneer==0.19
# via -r requirements/requirements-dev.in
virtualenv==20.4.0
# via tox
webencodings==0.5.1
# via bleach
werkzeug==1.0.1
# via flask
wheel==0.36.2
# via tox-wheel
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip # pip

View File

@@ -1,3 +1,4 @@
Flask # general purpose web service and web server stuff Flask # general purpose web service and web server stuff
Markdown # markdown rendering in templates Markdown # markdown rendering in templates
mdx-linkify # convert URLs in the text to clickable links
tzlocal # identifying system's local timezone tzlocal # identifying system's local timezone

View File

@@ -4,12 +4,35 @@
# #
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in # pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
# #
click==7.1.2 # via flask bleach==3.2.2
flask==1.1.2 # via -r requirements/requirements.in # via mdx-linkify
itsdangerous==1.1.0 # via flask click==7.1.2
jinja2==2.11.2 # via flask # via flask
markdown==3.3.3 # via -r requirements/requirements.in flask==1.1.2
markupsafe==1.1.1 # via jinja2 # via -r requirements/requirements.in
pytz==2020.4 # via tzlocal itsdangerous==1.1.0
tzlocal==2.1 # via -r requirements/requirements.in # via flask
werkzeug==1.0.1 # via flask jinja2==2.11.2
# via flask
markdown==3.3.3
# via
# -r requirements/requirements.in
# mdx-linkify
markupsafe==1.1.1
# via jinja2
mdx-linkify==2.1
# via -r requirements/requirements.in
packaging==20.8
# via bleach
pyparsing==2.4.7
# via packaging
pytz==2020.5
# via tzlocal
six==1.15.0
# via bleach
tzlocal==2.1
# via -r requirements/requirements.in
webencodings==0.5.1
# via bleach
werkzeug==1.0.1
# via flask

View File

@@ -23,20 +23,24 @@ def test_markdown_meta_extension_always():
assert b'<title>Index - incorporeal.org</title>' in response.data assert b'<title>Index - incorporeal.org</title>' in response.data
def test_extra_markdown_extensions_work(): def test_custom_markdown_extensions_work():
"""Test we can load more extensions via config, and that they work.""" """Test we can change extensions via config, and that they work.
This used to test smarty, but that's added by default now, so we test
that it can be removed by overriding the option.
"""
app = create_app(instance_path=os.path.join(HERE, 'instance')) app = create_app(instance_path=os.path.join(HERE, 'instance'))
client = app.test_client() client = app.test_client()
response = client.get('/mdash-or-triple-dash') response = client.get('/mdash-or-triple-dash')
assert response.status_code == 200 assert response.status_code == 200
assert b'word --- word' in response.data assert b'word &mdash; word' in response.data
app = create_app(instance_path=os.path.join(HERE, 'instance'), app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'MARKDOWN_EXTENSIONS': ['smarty']}) test_config={'MARKDOWN_EXTENSIONS': []})
client = app.test_client() client = app.test_client()
response = client.get('/mdash-or-triple-dash') response = client.get('/mdash-or-triple-dash')
assert response.status_code == 200 assert response.status_code == 200
assert b'word &mdash; word' in response.data assert b'word --- word' in response.data
def test_title_override(): def test_title_override():