- webmention sending is back (only for RE: for now) via Telegraph

- removed the abomination experiment (aka microdata)
- added svg source icomoon for the possibility to extend later
- rsync moved into nasg.py itself, so the ordering of render - sync - webmentions is ok
This commit is contained in:
Peter Molnar 2018-08-15 11:02:59 +01:00
parent 9c749f4591
commit 55cc0f3307
11 changed files with 331 additions and 247 deletions

2
.gitignore vendored
View file

@ -1,5 +1,5 @@
__pycache__
_scratch
keys.py
nasg.proj
.idea
.venv

Binary file not shown.

398
nasg.py
View file

@ -41,6 +41,8 @@ MarkdownImage = namedtuple(
['match', 'alt', 'fname', 'title', 'css']
)
REPLY_TYPES = ['webmention', 'in-reply-to', 'reply']
J2 = jinja2.Environment(
loader=jinja2.FileSystemLoader(searchpath=settings.paths.get('tmpl')),
lstrip_blocks=True,
@ -78,6 +80,58 @@ class cached_property(object):
setattr(inst, self.name, result)
return result
class Webmention(object):
def __init__(self, source, target, stime):
self.source = source
self.target = target
self.stime = stime
@property
def fpath(self):
return os.path.join(
settings.paths.get('webmentions'),
'%s => %s.txt' % (
url2slug(self.source, 100),
url2slug(self.target, 100)
)
)
@property
def exists(self):
if not os.path.isfile(self.fpath):
return False
elif os.path.getmtime(self.fpath) > self.stime:
return True
else:
return False
async def save(self, content):
d = os.path.dirname(self.fpath)
if not os.path.isdir(d):
os.makedirs(d)
with open(self.fpath, 'wt') as f:
f.write(content)
async def send(self):
if self.exists:
return
telegraph_url = 'https://telegraph.p3k.io/webmention'
telegraph_params = {
'token': '%s' % (keys.telegraph.get('token')),
'source': '%s' % (self.source),
'target': '%s' % (self.target)
}
r = requests.post(telegraph_url, data=telegraph_params)
settings.logger.info(
"sent webmention to telegraph from %s to %s",
self.source,
self.target
)
if r.status_code not in [200, 201, 202]:
settings.logger.error('sending failed: %s %s', r.status_code, r.text)
else:
await self.save(r.text)
class MarkdownDoc(object):
@cached_property
@ -95,12 +149,19 @@ class MarkdownDoc(object):
def content(self):
return self._parsed[1]
@property
def has_mainimg(self):
if hasattr(self, 'images') and len(self.images) == 1:
return True
else:
return False
@cached_property
def html_content(self):
c = "%s" % (self.content)
if hasattr(self, 'images') and len(self.images):
for match, img in self.images.items():
c = c.replace(match, str(img))
c = c.replace(match, img.mkstring(self.has_mainimg))
# return MD.reset().convert(c)
c = Pandoc(c)
c = RE_PRECODE.sub('<pre><code lang="\g<1>" class="language-\g<1>">', c)
@ -337,8 +398,6 @@ class Singular(MarkdownDoc):
@property
def syndicate(self):
urls = self.meta.get('syndicate', [])
if "https://brid.gy/publish/twitter" not in urls:
urls.append("https://brid.gy/publish/twitter")
if self.is_photo:
urls.append("https://brid.gy/publish/flickr")
return urls
@ -375,6 +434,14 @@ class Singular(MarkdownDoc):
return True
return False
@property
def to_ping(self):
urls = []
if self.is_reply:
w = Webmention(self.url, self.is_reply, self.mtime)
urls.append(w)
return urls
@property
def licence(self):
if self.category in settings.licence:
@ -404,15 +471,16 @@ class Singular(MarkdownDoc):
def replies(self):
r = OrderedDict()
for mtime, c in self.comments.items():
if c.type in ['webmention', 'in-reply-to']:
r[mtime] = c.tmplvars
if c.type not in REPLY_TYPES:
continue
r[mtime] = c.tmplvars
return r
@property
def reactions(self):
r = OrderedDict()
for mtime, c in self.comments.items():
if c.type in ['webmention', 'in-reply-to']:
if c.type in REPLY_TYPES:
continue
t = "%s" % (c.type)
if t not in r:
@ -450,7 +518,10 @@ class Singular(MarkdownDoc):
'has_code': self.has_code,
}
if (self.enclosure):
v.update({'enclosure': self.enclosure})
v.update({
'enclosure': self.enclosure,
'has_mainimg': self.has_mainimg
})
return v
@property
@ -488,6 +559,14 @@ class Singular(MarkdownDoc):
self.content,
])
#async def update(self):
#fm = frontmatter.loads('')
#fm.metadata = self.meta
#fm.content = self.content
#with open(fpath, 'wt') as f:
#settings.logger.info("updating %s", fpath)
#f.write(frontmatter.dumps(fm))
async def copyfiles(self):
copystatics( os.path.dirname(self.fpath))
@ -501,7 +580,6 @@ class Singular(MarkdownDoc):
'meta': settings.meta,
'licence': settings.licence,
'tips': settings.tips,
'labels': settings.labels
})
if not os.path.isdir(self.renderdir):
settings.logger.info("creating directory: %s", self.renderdir)
@ -530,7 +608,7 @@ class WebImage(object):
self.Resized(self, max(self.width, self.height))
))
def __str__(self):
def mkstring(self, is_mainimg=False):
if len(self.mdimg.css):
return self.mdimg.match
tmpl = J2.get_template("%s.j2.html" % (self.__class__.__name__))
@ -543,6 +621,7 @@ class WebImage(object):
'caption': self.caption,
'exif': self.exif,
'is_photo': self.is_photo,
'is_mainimg': is_mainimg
})
@cached_property
@ -847,18 +926,143 @@ class AsyncWorker(object):
def run(self):
self._loop.run_until_complete(asyncio.wait(self._tasks))
class IndexPHP(object):
def __init__(self):
self.gone = {}
self.redirect = {}
class PHPFile(object):
@property
def exists(self):
if settings.args.get('force'):
return False
if not os.path.exists(self.renderfile):
return False
if self.mtime > os.path.getmtime(self.renderfile):
return False
return True
@property
def mtime(self):
r = 0
if os.path.exists(self.renderfile):
r = os.path.getmtime(self.renderfile)
return r
return os.path.getmtime(
os.path.join(
settings.paths.get('tmpl'),
self.templatefile
)
)
@property
def renderfile(self):
raise ValueError('Not implemented')
@property
def templatefile(self):
raise ValueError('Not implemented')
async def render(self):
if self.exists:
return
await self._render()
class Search(PHPFile):
def __init__(self):
self.fpath = os.path.join(
settings.paths.get('build'),
'search.sqlite'
)
self.db = sqlite3.connect(self.fpath)
self.db.execute('PRAGMA auto_vacuum = INCREMENTAL;')
self.db.execute('PRAGMA journal_mode = MEMORY;')
self.db.execute('PRAGMA temp_store = MEMORY;')
self.db.execute('PRAGMA locking_mode = NORMAL;')
self.db.execute('PRAGMA synchronous = FULL;')
self.db.execute('PRAGMA encoding = "UTF-8";')
self.db.execute('''
CREATE VIRTUAL TABLE IF NOT EXISTS data USING fts4(
url,
mtime,
name,
title,
category,
content,
notindexed=category,
notindexed=url,
notindexed=mtime,
tokenize=porter
)'''
)
def __exit__(self):
self.db.commit()
self.db.execute('PRAGMA auto_vacuum;')
self.db.close()
def check(self, name):
ret = 0
maybe = self.db.execute('''
SELECT
mtime
FROM
data
WHERE
name = ?
''', (name,)).fetchone()
if maybe:
ret = int(maybe[0])
return ret
def append(self, url, mtime, name, title, category, content):
mtime = int(mtime)
check = self.check(name)
if (check and check < mtime):
self.db.execute('''
DELETE
FROM
data
WHERE
name=?''', (name,))
check = False
if not check:
self.db.execute('''
INSERT INTO
data
(url, mtime, name, title, category, content)
VALUES
(?,?,?,?,?,?);
''', (
url,
mtime,
name,
title,
category,
content
))
@property
def renderfile(self):
return os.path.join(
settings.paths.get('build'),
'search.php'
)
@property
def templatefile(self):
return 'Search.j2.php'
async def _render(self):
r = J2.get_template(self.templatefile).render({
'post': {},
'site': settings.site,
'author': settings.author,
'meta': settings.meta,
'licence': settings.licence,
'tips': settings.tips,
})
with open(self.renderfile, 'wt') as f:
settings.logger.info("rendering to %s", self.renderfile)
f.write(r)
class IndexPHP(PHPFile):
def __init__(self):
self.gone = {}
self.redirect = {}
def add_gone(self, uri):
self.gone[uri] = True
@ -878,8 +1082,12 @@ class IndexPHP(object):
'index.php'
)
async def render(self):
r = J2.get_template('Index.j2.php').render({
@property
def templatefile(self):
return 'Index.j2.php'
async def _render(self):
r = J2.get_template(self.templatefile).render({
'post': {},
'site': settings.site,
'gones': self.gone,
@ -890,7 +1098,7 @@ class IndexPHP(object):
f.write(r)
class WebhookPHP(object):
class WebhookPHP(PHPFile):
@property
def renderfile(self):
return os.path.join(
@ -898,8 +1106,12 @@ class WebhookPHP(object):
'webhook.php'
)
async def render(self):
r = J2.get_template('Webhook.j2.php').render({
@property
def templatefile(self):
return 'Webhook.j2.php'
async def _render(self):
r = J2.get_template(self.templatefile).render({
'author': settings.author,
'callback_secret': keys.webmentionio.get('callback_secret'),
})
@ -1084,7 +1296,6 @@ class Category(dict):
'meta': settings.meta,
'licence': settings.licence,
'tips': settings.tips,
'labels': settings.labels,
'category': self.tmplvars,
'pages': {
'current': pagenum,
@ -1119,103 +1330,6 @@ class Category(dict):
await self.render_feed()
class Search(object):
def __init__(self):
self.changed = False
self.fpath = os.path.join(
settings.paths.get('build'),
'search.sqlite'
)
self.db = sqlite3.connect(self.fpath)
self.db.execute('PRAGMA auto_vacuum = INCREMENTAL;')
self.db.execute('PRAGMA journal_mode = MEMORY;')
self.db.execute('PRAGMA temp_store = MEMORY;')
self.db.execute('PRAGMA locking_mode = NORMAL;')
self.db.execute('PRAGMA synchronous = FULL;')
self.db.execute('PRAGMA encoding = "UTF-8";')
self.db.execute('''
CREATE VIRTUAL TABLE IF NOT EXISTS data USING fts4(
url,
mtime,
name,
title,
category,
content,
notindexed=category,
notindexed=url,
notindexed=mtime,
tokenize=porter
)'''
)
def __exit__(self):
if (self.changed):
self.db.commit()
self.db.execute('PRAGMA auto_vacuum;')
self.db.close()
def exists(self, name):
ret = 0
maybe = self.db.execute('''
SELECT
mtime
FROM
data
WHERE
name = ?
''', (name,)).fetchone()
if maybe:
ret = int(maybe[0])
return ret
def append(self, url, mtime, name, title, category, content):
mtime = int(mtime)
exists = self.exists(name)
if (exists and exists < mtime):
self.db.execute('''
DELETE
FROM
data
WHERE
name=?''', (name,))
exists = False
if not exists:
self.db.execute('''
INSERT INTO
data
(url, mtime, name, title, category, content)
VALUES
(?,?,?,?,?,?);
''', (
url,
mtime,
name,
title,
category,
content
))
self.changed = True
async def render(self):
target = os.path.join(
settings.paths.get('build'),
'search.php'
)
if os.path.exists(target):
return
r = J2.get_template('Search.j2.php').render({
'post': {},
'site': settings.site,
'author': settings.author,
'meta': settings.meta,
'licence': settings.licence,
'tips': settings.tips,
'labels': settings.labels
})
with open(target, 'wt') as f:
settings.logger.info("rendering to %s", target)
f.write(r)
class Sitemap(dict):
@property
def mtime(self):
@ -1280,11 +1394,7 @@ def mkcomment(webmention):
fdir,
"%d-%s.md" % (
dt.timestamp,
slugify(
re.sub(r"^https?://(?:www)?", "", webmention.get('source')),
only_ascii=True,
lower=True
)[:200]
url2slug(webmention.get('source'))
)
)
@ -1336,6 +1446,14 @@ def makecomments():
pass
def url2slug(url, limit=200):
return slugify(
re.sub(r"^https?://(?:www)?", "", url),
only_ascii=True,
lower=True
)[:limit]
def make():
start = int(round(time.time() * 1000))
last = 0
@ -1344,8 +1462,9 @@ def make():
content = settings.paths.get('content')
worker = AsyncWorker()
rules = IndexPHP()
webmentions = AsyncWorker()
rules = IndexPHP()
for e in glob.glob(os.path.join(content, '*', '*.ptr')):
post = Gone(e)
if post.mtime > last:
@ -1356,16 +1475,11 @@ def make():
if post.mtime > last:
last = post.mtime
rules.add_redirect(post.source, post.target)
if rules.mtime < last or settings.args.get('force'):
worker.add(rules.render())
worker.add(rules.render())
webhook = WebhookPHP()
worker.add(webhook.render())
if rules.mtime < last or settings.args.get('force'):
worker.add(rules.render())
sitemap = Sitemap()
search = Search()
categories = {}
@ -1375,6 +1489,9 @@ def make():
post = Singular(e)
for i in post.images.values():
worker.add(i.downsize())
for i in post.to_ping:
webmentions.add(i.send())
worker.add(post.render())
worker.add(post.copyfiles())
if post.is_future:
@ -1400,11 +1517,10 @@ def make():
search.__exit__()
worker.add(search.render())
worker.add(sitemap.render())
for category in categories.values():
worker.add(category.render())
worker.add(sitemap.render())
worker.run()
settings.logger.info('worker finished')
@ -1427,7 +1543,17 @@ def make():
end = int(round(time.time() * 1000))
settings.logger.info('process took %d ms' % (end - start))
settings.logger.info('starting syncing')
os.system(
"rsync -avuhH --delete-after %s/ %s/" % (
settings.paths.get('build'),
settings.syncserver
)
)
settings.logger.info('syncing finished')
settings.logger.info('sending webmentions')
webmentions.run()
settings.logger.info('sending webmentions finished')
if __name__ == '__main__':
make()

View file

@ -1,23 +1,10 @@
arrow==0.12.1
bleach==2.1.3
certifi==2018.4.16
chardet==3.0.4
emoji==0.5.0
feedgen==0.7.0
html5lib==1.0.1
idna==2.7
Jinja2==2.10
langdetect==1.0.7
lxml==4.2.4
MarkupSafe==1.0
pkg-resources==0.0.0
python-dateutil==2.7.3
python-frontmatter==0.4.2
PyYAML==3.13
requests==2.19.1
six==1.11.0
unicode-slugify==0.1.3
Unidecode==1.0.22
urllib3==1.23
Wand==0.4.4
webencodings==0.5.1

2
run
View file

@ -3,5 +3,5 @@
set -euo pipefail
IFS=$'\n\t'
source ./.venv/bin/activate
#source ./.venv/bin/activate
python3 nasg.py "$@"

View file

@ -4,6 +4,7 @@ import argparse
import logging
base = os.path.abspath(os.path.expanduser('~/Projects/petermolnar.net'))
syncserver = 'liveserver:/web/petermolnar.net/web'
site = {
'title': 'Peter Molnar',
@ -16,10 +17,6 @@ site = {
'journal'
],
'licence': 'by-nc-nd',
'piwik': {
'domain': 'stats.petermolnar.net',
'id': 1
}
}
categorydisplay = {
@ -33,25 +30,12 @@ licence = {
'journal': 'by-nc',
}
labels = {
'tiptext': {
'photo':
"Did you like this photo?<br />"
"Leave a tip! If you're interested in prints, please get in touch.",
'article':
"Did you find this article useful?<br />"
"Support me, so I can write more like this.<br />"
"If you want my help for your project, get in touch.",
'journal':
"Did you like this entry?<br />"
"Encourage me to write more of them.",
}
}
meta = {
'webmention': 'https://webmention.io/petermolnar.net/webmention',
'pingback': 'https://webmention.io/petermolnar.net/xmlrpc',
'hub': 'https://petermolnar.superfeedr.com/'
'hub': 'https://petermolnar.superfeedr.com/',
'authorization_endpoint': 'https://indieauth.com/auth',
'token_endpoint': 'https://tokens.indieauth.com/token',
}
author = {
@ -61,14 +45,18 @@ author = {
'avatar': 'https://petermolnar.net/molnar_peter_avatar.jpg',
'gpg': 'https://petermolnar.net/pgp.asc',
'cv': 'https://petermolnar.net/about.html',
'xmpp': 'mail@petermolnar.net',
'flickr': 'petermolnareu',
'github': 'petermolnar',
'twitter': 'petermolnar'
'contact': {
'xmpp': 'xmpp:mail@petermolnar.net',
'tumblr': 'https://petermolnarnet.tumblr.com/',
'wordpress': 'https://petermolnareu.wordpress.com/',
'flickr': 'https://flickr.com/people/petermolnareu',
'github': 'https://github.com/petermolnar',
}
}
paths = {
'content': os.path.join(base, 'content'),
'webmentions': os.path.join(base, 'content', 'webmentions'),
'tmpl': os.path.join(base, 'nasg', 'templates'),
'watermark': os.path.join(base, 'nasg', 'templates', 'watermark.png'),
'build': os.path.join(base, 'www'),
@ -85,20 +73,10 @@ photo = {
},
}
tips = [
{
'name': 'paypal',
'label': 'PayPal',
'value': '£3',
'url': 'https://paypal.me/petermolnar/3GBP',
},
{
'name': 'monzo',
'label': 'Monzo (UK)',
'value': '£3',
'url': 'https://monzo.me/petermolnar/3',
},
]
tips = {
'paypal': 'https://paypal.me/petermolnar/3GBP',
'monzo': 'https://monzo.me/petermolnar/3',
}
dateformat = {
'iso': 'YYYY-MM-DDTHH:mm:ssZZ',

6
sync
View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
rsync -avuhH --delete-after ../www/ liveserver:/web/petermolnar.net/web/

View file

@ -19,26 +19,26 @@
{% endif %}
{% set _ = year.append(post.year)%}
<article class="h-entry hentry singular" lang="{{ post.lang }}" itemprop="blogPost" itemscope="" itemtype="http://schema.org/BlogPosting" itemref="site-publisher">
<article class="h-entry hentry singular" lang="{{ post.lang }}">
<header>
{% if category.display == 'flat' %}
<h3 itemprop="name headline" class="p-name entry-title">
<h3 class="p-name entry-title">
{% else %}
<h2 itemprop="name headline" class="p-name entry-title">
<h2 class="p-name entry-title">
{% endif %}
{% if post.is_reply %}
<svg class="icon" width="16" height="16">
<use xlink:href="#icon-reply" />
</svg>
<a href="{{ post.url }}/" class="u-url bookmark" itemprop="url mainEntityOfPage">
<a href="{{ post.url }}/" class="u-url u-uuid" rel="bookmark">
RE:
</a>
<a href="{{ post.is_reply }}" class="u-in-reply-to">
{{ post.is_reply }}
</a>
{% else %}
<a href="{{ post.url }}" title="{{ post.title }}" class="u-url bookmark" itemprop="url mainEntityOfPage">
<span class="entry-title p-name">{{ post.title }}</span>
<a href="{{ post.url }}" title="{{ post.title }}" rel="bookmark" class="u-url u-uuid">
{{ post.title }}
</a>
{% endif %}
{% if category.display == 'flat' %}
@ -49,7 +49,7 @@
</header>
{% if post.summary %}
<div class="e-summary entry-summary" itemprop="description">
<div class="e-summary entry-summary">
{{ post.html_summary }}
<p class="more">
<a href="{{ post.url }}" title="{{ post.title }}">
@ -58,7 +58,7 @@
</p>
</div>
{% else %}
<div class="e-content entry-content" itemprop="articleBody">
<div class="e-content entry-content">
{{ post.html_content }}
</div>
{% endif %}

View file

@ -2,7 +2,7 @@
{% if href != src %}
<a href="{{ href }}">
{% endif %}
<img src="{{ src }}" title="{{ title }}" alt="" width="{{ width }}" height="{{ height }}" {% if is_mainimg %}itemprop="image" class="u-featured"{% endif %} />
<img src="{{ src }}" title="{{ title }}" alt="" width="{{ width }}" height="{{ height }}"{% if is_mainimg %} class="u-featured"{% endif %} />
{% if href != src %}
</a>
{% endif %}

View file

@ -13,5 +13,21 @@ if(! isset($payload['secret']) || $payload['secret'] != '{{ callback_secret }}'
die('Bad Request');
}
mail("{{ author.email }}", "[webmention] {$payload['source']}", $raw);
$msg = sprintf('
Type: %s
Source: %s
Target: %s
From: %s
%s
',
$payload['post']['wm-property'],
$payload['source'],
$payload['target'],
$payload['post']['author']['name'],
$payload['post']['content']['text']
);
mail("{{ author.email }}", "[webmention] {$payload['source']}", $msg);
header('HTTP/1.1 202 Accepted');

View file

@ -36,7 +36,7 @@
}
</script>
</head>
<body itemscope="" itemtype="http://schema.org/Blog http://schema.org/WebPage">
<body>
{% macro activemenu(name) %}{% if (post is defined and post.category == name ) or ( category is defined and category.name == name ) %}active{% endif %}{% endmacro %}
@ -103,9 +103,9 @@
{% block content %}
<main>
<article class="h-entry hentry singular" lang="{{ post.lang }}" itemprop="blogPost" itemscope="" itemtype="http://schema.org/BlogPosting" itemref="site-publisher">
<article class="h-entry hentry singular" lang="{{ post.lang }}">
<header>
<h1 class="entry-title p-name" itemprop="name headline">
<h1 class="entry-title p-name">
{% if post.is_reply %}
<span>
<svg width="16" height="16">
@ -128,35 +128,32 @@
{% if post.review %}
<hr/>
<div class="h-review hreview" itemprop="review" itemscope="" itemtype="http://schema.org/Review">
<div class="h-review hreview">
<strong>Review summary of: <a href="{{ post.review.url }}" class="item fn p-name u-url p-item h-product">{{ post.review.title }}</a></strong>
<p>
By
<span class="p-author h-card vcard reviewer" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
<a class="fn p-name url u-url u-uid" href="{{ author.url }}" itemprop="url">
<span itemprop="name">{{ author.name }}</span>
</a></span> at <time class="dt-published dtreviewed" datetime="{{ post.pubtime }}" itemprop="datePublished">{{ post.pubdate }}</time>
<span class="p-author h-card vcard reviewer">
<a class="fn p-name url u-url u-uid" href="{{ author.url }}">{{ author.name }}</a></span> at <time class="dt-published dtreviewed" datetime="{{ post.pubtime }}">{{ post.pubdate }}</time>
</p>
<p>
<span class="rating" itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
<meta itemprop="worstRating" content = "1">
<span class="value" itemprop="ratingValue">{{ post.review.rating }}</span>
<span class="rating">
<span class="value">{{ post.review.rating }}</span>
out of
<span class="best" itemprop="bestRating">5</span>
<span class="best">5</span>
</span>
</p>
<p class="p-summary summary" itemprop="reviewBody">{{ post.review.summary }}</p>
<p class="p-summary summary">{{ post.review.summary }}</p>
</div>
<hr/>
{% endif %}
{% if post.summary %}
<div class="e-summary entry-summary" itemprop="description">
<div class="e-summary entry-summary">
{{ post.html_summary }}
</div>
{% endif %}
<div class="e-content entry-content" itemprop="articleBody">
<div class="e-content entry-content">
{{ post.html_content }}
</div>
@ -167,35 +164,27 @@
<dd class="published">
<time class="dt-published"
datetime="{{ post.pubtime }}"
itemprop="dateModified datePublished"
>{{ post.pubdate }}</time>
</dd>
<dt>Author</dt>
<dd>
<p class="p-author h-card vcard" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
<p class="p-author h-card vcard">
<img class="photo avatar u-photo u-avatar"
src="{{ author.avatar }}"
alt="Photo of {{ author.name }}"
itemprop="image" />
/>
<a class="fn p-name url u-url u-uid"
href="{{ author.url }}"
rel="author"
itemprop="url">
<span itemprop="name">{{ author.name }}</span>
</a>
<a class="u-email email" href="mailto:{{ author.email }}">
<span itemprop="email">{{ author.email }}</span>
</a>
>{{ author.name }}</a>
&lt;<a class="u-email email" href="mailto:{{ author.email }}">{{ author.email }}</a>&gt;
</p>
</dd>
<dt>Entry URL</dt>
<dd>
{% if not post.has_mainimg %}
<img aria-hidden="true" src="{{ author.avatar }}" itemprop="image" />
{% endif %}
<a class="u-url u-uuid" rel="bookmark" href="{{ post.url }}" itemprop="url mainEntityOfPage">
<a class="u-url u-uuid" rel="bookmark" href="{{ post.url }}">
{{ post.url }}
</a>
</dd>
@ -203,7 +192,7 @@
<dt>License</dt>
<dd class="license">
{% if post.licence == 'by' %}
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/" class="u-license" itemprop="license">CC BY 4.0</a>
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/" class="u-license">CC BY 4.0</a>
<ul>
<li>you can share it</li>
<li>you can republish it</li>
@ -212,7 +201,7 @@
<li>you always need to make a link back here</li>
</ul>
{% elif post.licence.text == 'by-nc' %}
<a rel="license" href="https://creativecommons.org/licenses/by-nc/4.0/" class="u-license" itemprop="license">CC BY-NC 4.0</a>
<a rel="license" href="https://creativecommons.org/licenses/by-nc/4.0/" class="u-license">CC BY-NC 4.0</a>
<ul>
<li>you can share it</li>
<li>you can republish it</li>
@ -222,7 +211,7 @@
</ul>
For commercial use, please contact me.
{% else %}
<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/" class="u-license" itemprop="license">CC BY-NC-ND 4.0</a>
<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/" class="u-license">CC BY-NC-ND 4.0</a>
<ul>
<li>you can share it</li>
<li>you can't modify it</li>
@ -332,27 +321,21 @@
{% endblock %}
<footer class="p-author h-card vcard" id="site-publisher" itemprop="publisher" itemscope="" itemtype="http://schema.org/Organization">
<footer class="p-author h-card vcard">
<p>
<a href="https://creativecommons.org/">CC</a>,
1999-2018,
<span itemprop="logo" itemscope itemtype="https://schema.org/ImageObject">
<img class="photo avatar u-photo u-avatar"
src="{{ author.avatar }}"
alt="Photo of {{ author.name }}"
itemprop="url" />
</span>
<a class="fn p-name url u-url u-uid" rel="me" href="{{ site.url }}/about.html" itemprop="url">
<span itemprop="name">{{ author.name }}</span></a>
<a rel="me" class="u-email email" href="mailto:{{ author.email }}">
<span itemprop="email">{{ author.email }}</span>
</a>
alt="Photo of {{ author.name }}" />
<a class="fn p-name url u-url u-uid" rel="me" href="{{ site.url }}/about.html">{{ author.name }}</a>
&lt;<a rel="me" class="u-email email" href="mailto:{{ author.email }}">{{ author.email }}</a>&gt;
</p>
<nav>
<ul>
{% for name, value in author.contact.items() %}
<li>
<a class="url u-url x-{{name}}" rel="me" href="{{ value }}" itemprop="sameAs">
<a class="url u-url x-{{name}}" rel="me" href="{{ value }}">
<svg width="16" height="16">
<use xlink:href="#icon-{{ name }}"></use>
</svg>