all repos — nasg @ c4f56088ccc8ac66abe933f64300394cfe879f0d

redesign, new home ideas
Peter Molnar hello@petermolnar.eu
Tue, 15 Jan 2019 22:28:58 +0100
commit

c4f56088ccc8ac66abe933f64300394cfe879f0d

parent

21c50a2e464e4f3a12b03de6ddc2c875ee2c69b1

M PipfilePipfile

@@ -6,16 +6,17 @@

[dev-packages] [packages] -arrow = "==0.12.1" -bleach = "==2.1.3" -emoji = "==0.5.0" -feedgen = "==0.7.0" -langdetect = "==1.0.7" -requests = "==2.19.1" -unicode-slugify = "==0.1.3" -Jinja2 = "==2.10" -Wand = "==0.4.4" +arrow = "*" +bleach = "*" +emoji = "*" +feedgen = "*" +langdetect = "*" +requests = "*" +unicode-slugify = "*" +Jinja2 = "*" +Wand = "*" pyyaml = "*" +python-frontmatter = "*" [requires] -python_version = "3.6" +python_version = "3.7"
M Pipfile.lockPipfile.lock

@@ -1,11 +1,11 @@

{ "_meta": { "hash": { - "sha256": "f5ff6b3262614c62925ce750d37d6e45b614a241837161740d54d9be6beb5252" + "sha256": "61a7889c295e0054b0526ddb11e48b5f8297c57bc7ce3196e9d49c602146e208" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.7" }, "sources": [ {

@@ -25,11 +25,11 @@ "version": "==0.12.1"

}, "bleach": { "hashes": [ - "sha256:b8fa79e91f96c2c2cd9fd1f9eda906efb1b88b483048978ba62fef680e962b34", - "sha256:eb7386f632349d10d9ce9d4a838b134d4731571851149f9cc2c05a9a837a9a44" + "sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718", + "sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9" ], "index": "pypi", - "version": "==2.1.3" + "version": "==3.0.2" }, "certifi": { "hashes": [

@@ -47,10 +47,11 @@ "version": "==3.0.4"

}, "emoji": { "hashes": [ - "sha256:001b92b9c8a157e1ca49187745fa450513bc8b31c87328dfd83d674b9d7dfa63" + "sha256:1e959336dafc7a5ed2c0256ee587bbd38a7187d772141f0b5ba42de9e08599a8", + "sha256:a9e9c08be9907c0042212c86dfbea0f61f78e9897d4df41a1d6307017763ad3e" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.5.1" }, "feedgen": { "hashes": [

@@ -59,19 +60,12 @@ ],

"index": "pypi", "version": "==0.7.0" }, - "html5lib": { - "hashes": [ - "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", - "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" - ], - "version": "==1.0.1" - }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "jinja2": { "hashes": [

@@ -159,6 +153,14 @@ "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"

], "version": "==2.7.5" }, + "python-frontmatter": { + "hashes": [ + "sha256:222203a9f56469c564b97b2d2607fde898b8546802d8361e729f087490761eff", + "sha256:94fc9ca449eddaae281455f2ba15662150c81d1e69c2f08a77ad11a5f38dfa1d" + ], + "index": "pypi", + "version": "==0.4.4" + }, "pyyaml": { "hashes": [ "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",

@@ -178,11 +180,11 @@ "version": "==3.13"

}, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.19.1" + "version": "==2.21.0" }, "six": { "hashes": [

@@ -207,18 +209,18 @@ "version": "==1.0.23"

}, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "version": "==1.23" + "version": "==1.24.1" }, "wand": { "hashes": [ - "sha256:28e0454c9d16d69c5d5034918d96320d8f9f1377b4fdaf4944eec2f938c74704", - "sha256:fb50d0ad4cad995f0b59b13f76bced22682ab80b4299084a2c3c535b225850c9" + "sha256:3e59e4bda9ef9d643d90e881cc950c8eee1508ec2cde1c150a1cbd5a12c1c007", + "sha256:52763dbf65d00cf98d7bc910b49329eea15896249c5555d47e169f2b6efbe166" ], "index": "pypi", - "version": "==0.4.4" + "version": "==0.5.0" }, "webencodings": { "hashes": [
M meta.pymeta.py

@@ -92,8 +92,8 @@ logging.info(

"calling Google classifyText for %s", self.fpath ) - r = requests.post(url, json=params) try: + r = requests.post(url, json=params) resp = r.json() for cat in resp.get('categories', []): self[cat.get('name')] = cat.get('confidence')

@@ -181,8 +181,9 @@ logging.info(

"calling Google Vision for %s", self.fpath ) - r = requests.post(url, json=params) + try: + r = requests.post(url, json=params) resp = r.json() for k, v in resp.items(): self[k] = v
M nasg.pynasg.py

@@ -25,12 +25,13 @@ import langdetect

import wand.image import jinja2 import yaml +import frontmatter from feedgen.feed import FeedGenerator from bleach import clean from emoji import UNICODE_EMOJI from slugify import slugify import requests -from pandoc import Pandoc +from pandoc import PandocMarkdown from meta import Exif, GoogleVision, GoogleClassifyText import settings import keys

@@ -187,13 +188,46 @@ self.save(r.text)

class MarkdownDoc(object): - mdregex = re.compile( - r'^---\s?[\r\n](?P<meta>.+?)[\r\n]---(?:\s?[\r\n](?P<content>.+))?', - flags=re.MULTILINE|re.DOTALL - ) + + @property + def regex(self): + return re.compile( + r'^---\s?[\r\n](?P<meta>.+?)[\r\n]---(?:\s?[\r\n](?P<content>.+))?', + flags=re.MULTILINE|re.DOTALL + ) + + @property + def mtime(self): + return os.path.getmtime(self.fpath) + + @property + def dt(self): + maybe = self.mtime + for key in ['published', 'date']: + t = self.meta.get(key, None) + if t and 'null' != t: + try: + t = arrow.get(t) + if t.timestamp > maybe: + maybe = t.timestamp + except Exception as e: + logger.error( + 'failed to parse date: %s for key %s in %s', + t, + key, + self.fpath + ) + return maybe @cached_property def _parsed(self): + with open(self.fpath, mode='rt') as f: + logger.debug('parsing YAML+MD file %s', self.fpath) + meta, txt = frontmatter.parse(f.read()) + return(meta, txt) + + @cached_property + def _reparsed(self): logger.debug('parsing file %s', self.fpath) with open(self.fpath, mode='r') as f: txt = f.read()

@@ -204,7 +238,7 @@ if txt.group('content'):

t = txt.group('content').strip() else: t = '' - return (yaml.load(txt.group('meta')), t) + return (yaml.safe_load(txt.group('meta')), t) @property def meta(self):

@@ -216,7 +250,7 @@ return self._parsed[1]

def __pandoc(self, c): if c and len(c): - c = Pandoc(c) + c = PandocMarkdown(c) c = RE_PRECODE.sub( '<pre><code lang="\g<1>" class="language-\g<1>">', c) return c

@@ -241,7 +275,6 @@

class Comment(MarkdownDoc): def __init__(self, fpath): self.fpath = fpath - self.mtime = os.path.getmtime(fpath) @property def dt(self):

@@ -339,16 +372,6 @@ self.fpath = fpath

n = os.path.dirname(fpath) self.name = os.path.basename(n) self.category = os.path.basename(os.path.dirname(n)) - self.mtime = os.path.getmtime(self.fpath) - - @property - def ctime(self): - ret = self.mtime - if len(self.comments): - for mtime, c in self.comments.items(): - if c.mtime > ret: - ret = c.mtime - return ret @cached_property def files(self):

@@ -368,6 +391,16 @@ and not k.endswith('.url')

and not k.endswith('.del') ] + @property + def updated(self): + maybe = self.dt + if len(self.comments): + for c in self.comments.values(): + + if c.dt > maybe: + maybe = c.dt + return maybe + @cached_property def comments(self): """

@@ -460,7 +493,7 @@ @cached_property

def html_summary(self): c = self.summary if c and len(c): - c = Pandoc(self.summary) + c = PandocMarkdown(self.summary) return c @property

@@ -667,12 +700,16 @@

@property def exists(self): if settings.args.get('force'): + logger.debug('rendering required: force mode on') return False elif not os.path.exists(self.renderfile): + logger.debug('rendering required: no html yet') return False - elif self.ctime > os.path.getmtime(self.renderfile): + elif self.dt > os.path.getmtime(self.renderfile): + logger.debug('rendering required: self.dt > html mtime') return False else: + logger.debug('rendering not required') return True @property

@@ -721,6 +758,42 @@ })

writepath(self.renderfile, r) +class Home(Singular): + def __init__(self, fpath): + super().__init__(fpath) + self.elements = [] + + def add(self, category, post): + self.elements.append((category.ctmplvars, post.tmplvars)) + + @property + def renderdir(self): + return settings.paths.get('build') + + @property + def renderfile(self): + return os.path.join( + settings.paths.get('build'), + 'index.html' + ) + + async def render(self): + if self.exists: + return + logger.info("rendering %s", self.name) + r = J2.get_template(self.template).render({ + 'post': self.tmplvars, + 'site': settings.site, + 'author': settings.author, + 'meta': settings.meta, + 'licence': settings.licence, + 'tips': settings.tips, + 'elements': self.elements + }) + writepath(self.renderfile, r) + + + class WebImage(object): def __init__(self, fpath, mdimg, parent): logger.debug("loading image: %s", fpath)

@@ -1228,7 +1301,7 @@ )

@property def templatefile(self): - return 'Index.j2.php' + return '404.j2.php' async def _render(self): r = J2.get_template(self.templatefile).render({

@@ -1379,7 +1452,7 @@ def newest(self, start=0, end=-1):

if start == end: end = -1 s = sorted( - [self[k].mtime for k in self.sortedkeys[start:end]], + [self[k].dt for k in self.sortedkeys[start:end]], reverse=True ) return s[0]

@@ -1395,6 +1468,16 @@ 'url': url,

'label': label } + @property + def ctmplvars(self): + return { + 'name': self.name, + 'display': self.display, + 'url': self.url, + 'feed': self.feedurl, + 'title': self.title, + } + def tmplvars(self, posts=[], c=False, p=False, n=False): if p: p = self.navlink(p)

@@ -1576,9 +1659,8 @@ )

) writepath(fpath, r) - async def render(self): - newest = self.newest() - if not self.is_uptodate(self.rssfeedfpath, newest): + async def render_feeds(self): + if not self.is_uptodate(self.rssfeedfpath, self.newest()): logger.info( '%s RSS feed outdated, generating new', self.name

@@ -1590,7 +1672,7 @@ '%s RSS feed up to date',

self.name ) - if not self.is_uptodate(self.atomfeedfpath, newest): + if not self.is_uptodate(self.atomfeedfpath, self.newest()): logger.info( '%s ATOM feed outdated, generating new', self.name

@@ -1602,8 +1684,11 @@ '%s ATOM feed up to date',

self.name ) + + async def render(self): + await self.render_feeds() if self.display == 'flat': - if not self.is_uptodate(self.indexfpath(), newest): + if not self.is_uptodate(self.indexfpath(), self.newest()): logger.info( '%s flat index outdated, generating new', self.name

@@ -1707,11 +1792,15 @@ url2slug(webmention.get('source'))

) ) + author = webmention.get('data', {}).get('author', None) + if not author: + logger.error('missing author info on webmention; skipping') + return meta = { 'author': { - 'name': webmention.get('data').get('author').get('name', ''), - 'url': webmention.get('data').get('author').get('url', ''), - 'photo': webmention.get('data').get('author').get('photo', '') + 'name': author.get('name', ''), + 'url': author.get('url', ''), + 'photo': author.get('photo', '') }, 'date': dt.format(settings.dateformat.get('iso')), 'source': webmention.get('source'),

@@ -1763,7 +1852,7 @@ sitemap = Sitemap()

search = Search() categories = {} frontposts = Category() - categories['/'] = frontposts + home = Home(settings.paths.get('home')) for e in sorted(glob.glob(os.path.join(content, '*', '*', 'index.md'))): post = Singular(e)

@@ -1779,6 +1868,7 @@ queue.put(post.copyfiles())

# skip draft posts from anything further if post.is_future: + logger.info('%s is for the future', post.name) continue # add post to search database

@@ -1823,13 +1913,18 @@ queue.put(rules.render())

# render categories for category in categories.values(): + home.add(category, category.get(category.sortedkeys[0])) queue.put(category.render()) + queue.put(frontposts.render_feeds()) + queue.put(home.render()) # actually run all the render & copy tasks queue.run() # copy static files for e in glob.glob(os.path.join(content, '*.*')): + if e.endswith('.md'): + continue t = os.path.join(settings.paths.get('build'),os.path.basename(e)) if os.path.exists(t) and os.path.getmtime(e) <= os.path.getmtime(t): continue
M pandoc.pypandoc.py

@@ -7,7 +7,7 @@

import subprocess import logging -class Pandoc(str): +class PandocMarkdown(str): def __new__(cls, text): """ Pandoc command line call with piped in- and output """ cmd = (

@@ -49,3 +49,45 @@ stderr

) r = stdout.decode('utf-8').strip() return str.__new__(cls, r) + +class PandocHTML(str): + def __new__(cls, text): + """ Pandoc command line call with piped in- and output """ + cmd = ( + 'pandoc', + '-o-', + '--to=markdown+%s' % ( + '+'.join([ + 'footnotes', + 'pipe_tables', + 'strikeout', + #'superscript', + #'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ]) + ), + '--from=html', + '--quiet', + ) + p = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + stdout, stderr = p.communicate(input=text.encode()) + if stderr: + logging.warning( + "Error during pandoc covert:\n\t%s\n\t%s", + cmd, + stderr + ) + r = stdout.decode('utf-8').strip() + return str.__new__(cls, r)
M settings.pysettings.py

@@ -71,6 +71,7 @@ 'remotewww': 'web',

'remotequeue': 'queue', 'micropub': os.path.join(base, 'content', 'note'), 'tmp': os.path.join(base, 'tmp'), + 'home': os.path.join(base, 'content', 'home', 'index.md'), } photo = {

@@ -95,19 +96,13 @@ 'display': 'YYYY-MM-DD HH:mm',

'fname': 'YYYYMMDDHHmmssZ', } -loglevels = { - 'critical': 50, - 'error': 40, - 'warning': 30, - 'info': 20, - 'debug': 10 -} - _parser = argparse.ArgumentParser(description='Parameters for NASG') _booleanparams = { 'regenerate': 'force downsizing images', 'force': 'force rendering HTML', 'nosync': 'skip sync to live server', + 'debug': 'set logging to debug level', + 'quiet': 'show only errors' } for k, v in _booleanparams.items():

@@ -118,20 +113,21 @@ default=False,

help=v ) -_parser.add_argument( - '--loglevel', - default='info', - help='change loglevel' -) - args = vars(_parser.parse_args()) -loglevel = loglevels.get(args.get('loglevel')) +if args.get('debug', False): + loglevel = 10 +elif args.get('quiet', False): + loglevel = 40 +else: + loglevel = 20 logger = logging.getLogger('NASG') logger.setLevel(loglevel) + console_handler = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') console_handler.setFormatter(formatter) logger.addHandler(console_handler) + logging.getLogger('asyncio').setLevel(loglevel)
M templates/Category.j2.htmltemplates/Category.j2.html

@@ -9,6 +9,7 @@ {% block title %}{{ category.title }}{% endblock %}

{% block meta %} <link rel="alternate" type="application/rss+xml" title="{{ category.title }} RSS feed" href="{{ category.feed }}" /> <link rel="alternate" type="application/atom+xml" title="{{ category.title }} ATOM feed" href="{{ category.feed }}atom.xml" /> + <link rel="feed" title="{{ category.title}} feed" href="{{ category.url }}" /> {% endblock %} {% block content %} <main class="content-body h-feed hfeed {{ category.name }}" property="h-feed">
A templates/Home.j2.html

@@ -0,0 +1,98 @@

+{% extends "base.j2.html" %} +{% block meta %} + <meta name="author" content="{{ author.name }} <{{ author.email }}>" /> + <meta name="description" content="{{ post.summary|e }}" /> + <link rel="canonical" href="{{ post.url }}" /> + {% for category, latest in elements %} + <link rel="alternate" type="application/rss+xml" title="{{ category.title }} RSS feed" href="{{ category.feed }}" /> + <link rel="alternate" type="application/atom+xml" title="{{ category.title }} ATOM feed" href="{{ category.feed }}atom.xml" /> + <link rel="feed" title="{{ category.title}} feed" href="{{ category.url }}" /> + {% endfor %} +{% endblock %} +{% block licence %} + <link rel="license" href="https://spdx.org/licenses/{{ post.licence }}.html" type="{{ post.licence }}" /> +{% endblock %} + +{% block content %} +<main class="content-body h-feed hfeed" property="h-feed"> + <aside> + <div> + {{ post.html_content }} + </div> + </aside> + + {% for category, latest in elements %} + <section> + <article class="h-entry hentry singular" property="h-entry" lang="{{ latest.lang }}" itemprop="blogPost" itemscope="" itemtype="http://schema.org/BlogPosting" itemref="author"> + <header> + <h2 class="p-name entry-title" property="p-name" itemprop="name headline" > + Latest in + <a href="{{ category.url }}/"> + <svg width="16" height="16"><use xlink:href="#icon-{{ category.name }}" /></svg> + {{ category.name }} + </a><br /> + {% if latest.is_reply %} + <svg class="icon" width="16" height="16"> + <use xlink:href="#icon-reply" /> + </svg> + <a href="{{ latest.url }}/" class="u-url bookmark" property="u-url" itemprop="url mainEntityOfPage"> + RE: + </a> + <a href="{{ latest.is_reply }}" class="u-in-reply-to" property="u-in-reply-to"> + {{ latest.is_reply }} + </a> + {% else %} + <a href="{{ latest.url }}" title="{{ latest.title }}" class="u-url bookmark" property="u-url" itemprop="url mainEntityOfPage"> + <span class="entry-title p-name" property="p-name">{{ latest.title }}</span> + </a> + {% endif %} + </h2> + </header> + + {% if latest.summary %} + <div class="e-summary entry-summary" property="e-summary" itemprop="description"> + {{ latest.html_summary }} + <span class="more"> + <a href="{{ latest.url }}" title="{{ latest.title }}"> + {% if latest.lang == 'hu' %}Tovább »{% else %}Continue »{% endif %} + </a> + </span> + </div> + {% else %} + <div class="e-content entry-content" property="e-content" itemprop="articleBody"> + {{ latest.html_content }} + </div> + {% endif %} + + <footer aria-hidden="true" hidden="hidden"> + <span class="published updated"> + <time class="dt-published dt-updated" property="dt-published dt-updated" datetime="{{ latest.pubtime }}" itemprop="dateModified datePublished">{{ latest.pubdate }}</time> + </span> + {% if not latest.has_mainimg %} + <img src="{{ author.avatar }}" + itemprop="image" + width="0" + height="0" + alt="Photo of {{ author.name }}" /> + {% endif %} + <p class="p-author h-card vcard" property="p-author h-card"> + <img class="photo avatar u-photo u-avatar" + property="u-photo u-avatar" + src="{{ author.avatar }}" + alt="Photo of {{ author.name }}" /> + <a class="fn p-name url u-url u-uid" + property="p-name u-url u-uid" + href="{{ author.url }}" + rel="author"> + {{ author.name }} + </a> + <a class="u-email email" property="u-email" href="mailto:{{ author.email }}"> + {{ author.email }} + </a> + </p> + </footer> + </article> + </section> +{% endfor %} +</main> +{% endblock %}
M templates/base.j2.htmltemplates/base.j2.html

@@ -12,7 +12,7 @@ <link rel="{{ key }}" href="{{ value }}" />

{% endfor %} {% block meta %}{% endblock %} <style media="all"> - {% include 'style.css' %} + {% include 'style-experiment.css' %} </style> <style id="css_alt" media="speech"> {% include 'style-alt.css' %}

@@ -30,7 +30,7 @@ <section>

<nav> <ul> <li> - <a title="home" href="{{ site.url }}/" class="{{ activemenu('') }}"> + <a title="home" href="{{ site.url }}/" class="{{ activemenu('home') }}"> <svg width="16" height="16"><use xlink:href="#icon-home" /></svg> home </a>

@@ -126,7 +126,6 @@ </h1>

</header> {% if post.review %} - <hr/> <div class="h-review hreview" property="h-review" itemprop="review" itemscope="" itemtype="http://schema.org/Review"> <strong>Review summary of: <a href="{{ post.review.url }}" class="item fn p-name u-url p-item h-product" property="p-name u-url p-item h-product">{{ post.review.title }}</a></strong> <p>

@@ -146,7 +145,6 @@ </span>

</p> <p class="p-summary summary" property="p-summary" itemprop="reviewBody">{{ post.review.summary }}</p> </div> - <hr/> {% endif %} {% if post.summary %}

@@ -159,7 +157,6 @@ <div class="e-content entry-content" property="e-content" itemprop="articleBody">

{{ post.html_content }} </div> - <hr/> <footer> <dl> {% if post.event %}

@@ -180,8 +177,7 @@ </dd>

{% endif %} <dt>Author</dt> - <dd> - <p class="p-author h-card vcard" property="p-author h-card" itemprop="author" itemscope="" itemtype="http://schema.org/Person"> + <dd class="p-author h-card vcard" property="p-author h-card" itemprop="author" itemscope="" itemtype="http://schema.org/Person"> <img class="photo avatar u-photo u-avatar" property="p-author h-card" src="{{ author.avatar }}"

@@ -194,10 +190,16 @@ rel="author"

itemprop="url"> <span itemprop="name">{{ author.name }}</span> </a> - &lt;<a class="u-email email" property="u-email" href="mailto:{{ author.email }}"> + <a class="u-email email" property="u-email" href="mailto:{{ author.email }}"> <span itemprop="email">{{ author.email }}</span> - </a>&gt; - </p> + </a> + <p aria-hidden="true" hidden="hidden" class="hidden" itemprop="publisher" itemscope="" itemtype="https://schema.org/Organization"> + <span itemprop="name">{{ site.domain }}</span> + <a href="{{ site.url }}" itemprop="url">{{ site.url }}</a> + <span itemprop="logo" itemscope="" itemtype="https://schema.org/ImageObject"> + <img src="{{ author.avatar }}" alt="" itemprop="url" /> + </span> + </p> </dd> <dt>Published</dt>

@@ -247,7 +249,7 @@

<dt>Entry URL</dt> <dd> {% if not post.has_mainimg %} - <img aria-hidden="true" src="{{ author.avatar }}" itemprop="image" hidden="hidden" /> + <img aria-hidden="true" src="{{ author.avatar }}" itemprop="image" hidden="hidden" class="hidden" /> {% endif %} <a class="u-url u-uuid" property="u-url u-uuid" rel="bookmark" href="{{ post.url }}" itemprop="url mainEntityOfPage"> {{ post.url }}

@@ -255,13 +257,6 @@ </a>

</dd> </dl> - <p aria-hidden="true" hidden="hidden" itemprop="publisher" itemscope="" itemtype="https://schema.org/Organization"> - <span itemprop="name">{{ site.domain }}</span> - <a href="{{ site.url }}" itemprop="url">{{ site.url }}</a> - <span itemprop="logo" itemscope="" itemtype="https://schema.org/ImageObject"> - <img src="{{ author.avatar }}" alt="" itemprop="url" /> - </span> - </p> </footer> {% if not post.is_page %}

@@ -274,25 +269,22 @@ </section>

{% endif %} <section class="encourage"> - <hr /> <h2>Encourage creation!</h2> <p>If this entry helped you, or you simply liked it, leave a tip.</p> - <nav> - <ul> - <li> - <a rel="payment" href="https://paypal.me/petermolnar/3GBP"> - <svg width="16" height="16"> - <use xlink:href="#icon-paypal"></use> - </svg> Paypal</a> - </li> - <li> - <a rel="payment" href="https://monzo.me/petermolnar/3"> - <svg width="16" height="16"> - <use xlink:href="#icon-monzo"></use> - </svg> Monzo (UK or Google Pay)</a> - </li> - </ul> - </nav> + <ul> + <li> + <a rel="payment" href="https://paypal.me/petermolnar/3GBP"> + <svg width="16" height="16"> + <use xlink:href="#icon-paypal"></use> + </svg> Paypal</a> + </li> + <li> + <a rel="payment" href="https://monzo.me/petermolnar/3"> + <svg width="16" height="16"> + <use xlink:href="#icon-monzo"></use> + </svg> Monzo (UK or Google Pay)</a> + </li> + </ul> </section> {% if post.replies|length %}

@@ -454,6 +446,7 @@ </footer>

<script> {% include 'themeswitcher.js' %} +{% include 'konami.js' %} </script> {% include 'symbols.svg' %}
A templates/konami.js

@@ -0,0 +1,18 @@

+function kcl(cb) { + var input = ''; + var key = '38384040373937396665'; + document.addEventListener('keydown', function (e) { + input += ("" + e.keyCode); + if (input === key) { + return cb(); + } + if (!key.indexOf(input)) return; + input = ("" + e.keyCode); + }); +} + +kcl(function () { + var e = document.createElement('img'); + e.src = '/iddqd.gif'; + document.body.appendChild(e); +})
M templates/style-alt.csstemplates/style-alt.css

@@ -1,3 +1,4 @@

+ body { color: #222; background-color: #eee;

@@ -34,20 +35,31 @@ th, tr:nth-child(even) {

background-color: rgba(0, 0, 0, .1); } -pre> code::before { +pre > code::before { color: #444; } + +.footnotes hr::before { + color: #222; +} body > header, -body > footer { - background-color: #333 +body > footer{ + background-color: #333; + color: #ccc; } -body > footer a { +body > header a, +body > footer a, +.theme input + label { color: #71B3F4; } +input { + background-color: #222; +} + body > footer a:hover { color: #fff; border-bottom: 1px solid #fff; -} +}
M templates/style.csstemplates/style.css

@@ -2,62 +2,136 @@ * {

-webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; + font-family: "Courier New", "Courier", monospace; + font-size: 100%; + margin: 0; + padding: 0; + line-height: 1.5em; } -html { +body { + color: #ccc; + background-color: #26272A; +} + +body > header, +body > footer { background-color: #111; + text-align: center; + padding: 0.6em 0; +} + +body > main, +body > nav, +body > header > section, +body > footer > section { + max-width: 88ch; + margin: 0 auto; + padding: 0 1em; +} + +hr { + border: none; +} + +dt { + font-weight: bold; +} + +h1, h2, h3, h4, h5, h6, hr, +dt { + margin: 2em 0 0.6em 0; +} + +main p { + margin: 1em 0; +} + +h1 { + border-bottom: 4px double #999; +} + +article > footer > dl > dt, +h2 { + border-bottom: 1px solid #999; } -body { - margin: 0; - padding: 0; - font-family: sans-serif; - color: #ccc; - background-color: #26272A; - line-height: 1.4em; - position: relative; +article > footer > dl > dt, +h3, +hr { + border-bottom: 1px dotted #999; +} + +h4 { + border-bottom: 1px dashed #999; } svg { transform: rotate(0deg); fill: currentColor; - vertical-align: middle; + vertical-align: text-top; +} + +body > svg { + display: none; } a { color: #f90; - /*color: #5193D4;*/ text-decoration: none; border-bottom: 1px solid transparent; - } a:hover { - color: #fff; - border-bottom: 1px solid #fff; + color: #eee; + border-bottom: 1px solid #eee; } -h1 { - font-size: 1.6em; +sup { + vertical-align: unset; +} + +sup:before { + content: '['; +} + +sup:after { + content: ']'; +} + +input, button, label { + -webkit-appearance:none; +} + +nav > ul { + list-style-type: none; +} + +nav > ul > li { + display: inline-block; } -h1, h2 { - line-height: 1.2em; +body > header form { + display: inline-block; + padding-left: 0.6em; + margin-top: 1em; } -h2 { - font-size: 1.2em; +body > header a { + font-weight: bold; + border-bottom: 3px solid transparent; + padding-bottom: 0.1em; } -h2, h3 { - margin-top: 2em; +body > header a:hover, +body > header a.active { + border-bottom: 3px solid #eee; + color: #eee; } -hr { - border: none; - border-top: 1px solid #777; - margin: 1em 0; - clear:both; +body > header a svg { + display: block; + margin: 0.1em auto; } blockquote {

@@ -67,27 +141,42 @@ padding: 0 0 0 1em;

color: #aaa; } -dt { - font-weight: bold; - margin: 1em 0 0.6em 0; +input { + width: 8em; + padding: 0 0.3em; + border: none; + background-color: #333; + color: #ccc; } -dd img { - width: 1em; - height: auto; +.hidden, .theme, +.theme input, input[type=submit] { + display: none; } -figure { - margin: 2em 0; +.theme input + label { + color: #f90; + cursor: pointer; + border-bottom: 3px solid transparent; + padding-bottom: 0.1em; } -figure > a { - border: none; +.theme input:hover + label, +.theme input:checked + label { + border-bottom: 3px solid #eee; + color: #eee; +} + +body > footer { + margin-top: 2em; +} + +body > footer > section > div > * { + margin: 0.6em 0; } -figcaption { - padding: 0.3em 0; - text-align: center; +body > footer .email span { + display: none; } video,

@@ -101,63 +190,33 @@ margin: 0 auto;

border: 1px solid #000; } -figcaption dd, label { - display: inline-block; - margin:0.3em 0.3em; +figure { + margin: 2em 0; } -label { - cursor: pointer; +figcaption { + margin-top: 1em; } -ul { - padding-left: 1.3em; +figcaption > dl { + margin-top: 1em; + color: #666; } -li { - padding: 0.1em 0; +figcaption > dl * { + display: inline-block; } -li p { - margin: 0; +figcaption > dl dt { + display: none; } -input, button, label { - border: none; - color: #ccc; - background-color: transparent; - vertical-align: middle; - -webkit-appearance:none; +figcaption > dl dd { + margin: 0 0.3em; } -input { - border-bottom: 3px solid #ccc; - margin: 0 0 0 0.3em; -} - -figcaption > ul, -nav ul { - list-style-type: none; - margin: 0; - padding: 0; -} - -figcaption > ul { - display:none; - text-align: right; -} - -figcaption ul li { - display: inline-block; -} - -nav li { - display: inline-block; - padding: 0 0.6em 0 0; -} - -code, pre, q, figcaption { - font-family: monospace; +footer img { + height: 1em; } code, pre {

@@ -173,10 +232,11 @@

pre { padding: 0.6em; position: relative; + margin: 1em 0; } code { - padding: 0.1em 0.2em; + padding: 0.05em 0.2em; } pre > code {

@@ -189,17 +249,16 @@ float: right;

color: #999; border-left: 1px solid #666; border-bottom: 1px solid #666; - padding-left: 0.3em; + padding: 0 0.3em; + margin: -0.6em -0.6em 0 0; } table { border-collapse: collapse; - border-spacing: 0; width: 100%; } -td, -th { +td, th { padding: 0.3em; border: 1px solid #777; text-align:left;

@@ -213,105 +272,30 @@ th, tr:nth-child(even) {

background-color: rgba(255, 255, 255, .1); } -body > header, -body > footer { - padding: 0.6em 0.6em; - background-color: #111; - text-align: center; -} - -body > header nav { - margin: 0.3em 0; -} - -body > header form { - color: #ccc; - display: inline-block; - margin: 0.6em 0 0 0; - padding-left: 0.6em; -} - -body > header a { - font-weight: bold; - color: #ccc; - border-bottom: 3px solid transparent; - text-decoration: none; - padding-bottom: 0.3em; -} - -body > header a:hover, -body > header a.active, -input:active, -input:hover { - border-bottom: 3px solid #fefefe; - color: #fefefe; -} - -body > header a svg { - display: block; - margin: 0.1em auto; -} - -.theme input + label { - border-bottom: 3px solid transparent; -} - -.theme input:checked + label { - border-bottom: 3px solid #ccc; -} - -input[type=submit] { - display: none; -} - -footer img { - width: 1em; -} - -body > main, -body > nav, -body > header > section, -body > footer > section { - max-width: 86ch; - margin: 0 auto; - padding: 0 0.6em; +main ul { + margin-left: 2em; } -body > header > section > * { - font-size: 0.9em; +main ol { + margin-left: 3em; } -body > nav { - padding: 0.6em; +li p { + margin: 0; } -body > nav ul { - text-align: center; +.encourage { + color: #090; } -body > nav li { - margin: 1em 0.6em; +.encourage h2 { + border-color: #090; } -body > nav .current { - color: #999; -} - -body > footer { +.footnotes hr:before { + content: 'Links'; color: #ccc; -} - -body > footer li span { - display: none; -} - -body > footer > section > * { - font-family: monospace; - font-size: 1.1em; -} - -body > footer nav { - margin: 1em 0; + font-weight: bold; } .replies .u-url,

@@ -321,46 +305,11 @@ overflow: hidden;

white-space: nowrap; text-overflow: ellipsis; vertical-align: top; - max-width: 80%; + max-width: 96%; } .footnote-back { - margin: 0 0 0 0.1em; -} - -.encourage { - text-align: center; - padding-bottom: 2em; -} - -.encourage h2 { - color: #080; -} - -.encourage li { - margin: 0 0.6em; -} - -.h-feed > article > footer { - display: none; -} - -figcaption dt, -body > svg, -body > script, -body > footer h2, -.h-feed > header > h1, -.theme, -.theme input { - display: none; -} - -body main h1 { - text-align: center; -} - -body main > aside { - margin-bottom: 4em; + margin: 0 0 0 0.6em; } @media all and (min-width: 58em) {

@@ -371,19 +320,13 @@ display: flex;

justify-content:space-between; } - body > header form, - body > footer > section > nav { - margin: 0; - } - body > header a svg { display: inline-block; } - - body main section { - /*! padding-top: 1em; */ - } + body > header form { + margin-top: 0; + } } body > img {

@@ -392,4 +335,4 @@ bottom: 0;

right: 0; width: 10em; height: auto; -}+}
M templates/themeswitcher.jstemplates/themeswitcher.js

@@ -29,14 +29,20 @@ if ((mode == DEFAULT_THEME && !mql.matches) || (mode == ALT_THEME && mql.matches)) {

localStorage.removeItem(STORAGE_KEY); } else { - localStorage.setItem(STORAGE_KEY, mode); + if(confirm("I\'ll need to store your theme of choice in your browser, in a place called localStorage.\n\nAre you OK with this?")) { + localStorage.setItem(STORAGE_KEY, mode); + } } autoTheme(mql); } function autoTheme(e) { var mode = DEFAULT_THEME; - var current = localStorage.getItem(STORAGE_KEY); + try { + var current = localStorage.getItem(STORAGE_KEY); + } catch(e) { + var current = DEFAULT_THEME; + } if ( current != null) { mode = current; }

@@ -51,30 +57,17 @@ var mql = window.matchMedia('(prefers-color-scheme: ' + ALT_THEME + ')');

autoTheme(mql); mql.addListener(autoTheme); -for(var i = colorscheme.length; i--; ) { - colorscheme[i].onclick = setTheme; -} - -var themeforms = document.getElementsByClassName(STORAGE_KEY); -for(var i = themeforms.length; i--; ) { - themeforms[i].style.display = 'inline-block'; -} - -function kcl(cb) { - var input = ''; - var key = '38384040373937396665'; - document.addEventListener('keydown', function (e) { - input += ("" + e.keyCode); - if (input === key) { - return cb(); +var test = 'ping'; +try { + localStorage.setItem(test, test); + localStorage.removeItem(test); + for(var i = colorscheme.length; i--; ) { + colorscheme[i].onclick = setTheme; + } + var themeforms = document.getElementsByClassName(STORAGE_KEY); + for(var i = themeforms.length; i--; ) { + themeforms[i].style.display = 'inline-block'; } - if (!key.indexOf(input)) return; - input = ("" + e.keyCode); - }); +} catch(e) { + console.log('localStorage is not available, manual theme switching is disabled'); } - -kcl(function () { - var e = document.createElement('img'); - e.src = '/iddqd.gif'; - document.body.appendChild(e); -})