Browse Source

Add cache generator and templates

master
David Larlet 1 year ago
commit
7f30f93aa0
No known key found for this signature in database
6 changed files with 825 additions and 0 deletions
  1. 151
    0
      cache.py
  2. 5
    0
      requirements.txt
  3. 603
    0
      templates/base_2019.html
  4. 31
    0
      templates/cache_archives.html
  5. 30
    0
      templates/cache_article.html
  6. 5
    0
      templates/cache_article.md

+ 151
- 0
cache.py View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python3

import codecs
import fnmatch
import hashlib
import os
from dataclasses import dataclass
from pathlib import Path
from time import perf_counter

import httpx
import lxml
import markdown
from jinja2 import Environment as Env
from jinja2 import FileSystemLoader
from minicli import cli, run, wrap
from readability.readability import Document

HERE = Path(".")
CACHE_PATH = HERE / "cache"


environment = Env(loader=FileSystemLoader(str(HERE / "templates")))


def parse_markdown(file_path):
"""Extract title, (HTML) content and metadata from a markdown file."""
parser = markdown.Markdown(extensions=["meta"])
with codecs.open(file_path, "r") as source:
content = parser.convert(source.read())
metadata = parser.Meta if hasattr(parser, "Meta") else None
title = metadata["title"][0] if metadata is not None else ""
return title, content, metadata


def each_markdown_from(source_dir, file_name="index.md"):
"""Walk across the `source_dir` and return the md file paths."""
for root, dirnames, filenames in os.walk(source_dir):
for filename in fnmatch.filter(filenames, file_name):
yield os.path.join(root, filename)


@dataclass
class Cache:
title: str
content: str
url: str
hash_url: str

@staticmethod
def all(source_dir=CACHE_PATH):
for file_path in each_markdown_from(source_dir):
title, content, metadata = parse_markdown(file_path)
url = metadata["url"][0]
hash_url = metadata["hash_url"][0]
yield Cache(title, content, url, hash_url)

@staticmethod
def one(hash_url):
return next(Cache.all(source_dir=CACHE_PATH / hash_url))


def extract_page(url):
"""From an URL, extract title and content using Readability.

The title is shortened through the `short_title` native method.
The content doesn't contain `<body>` tags to be directly
embeddable in the template and rendered as is.
"""
# Retrieves the resource and turns it into a Readability doc.
response = httpx.get(url)
document = Document(response.text)

# The short title is more concise and readable.
title = document.short_title()
content = document.summary(html_partial=True)

# Removing the added <div> and spaces.
content = content[5:-6].strip()
return title, content


def create(hash_url):
"""Turn new MD file into HTML file."""
template = environment.get_template("cache_article.html")
cache = Cache.one(hash_url)
page = template.render(cache=cache)
cache_target = CACHE_PATH / hash_url
if not os.path.exists(cache_target):
os.makedirs(cache_target)
open(cache_target / "index.html", "w").write(page)
print(f"Done: http://larlet.test:8001/david/cache/{hash_url}/")


@cli
def generate():
"""Generate caches MD files into HTML files."""
caches = []
template = environment.get_template("cache_article.html")
for cache in Cache.all():
page = template.render(cache=cache)
open(CACHE_PATH / cache.hash_url / "index.html", "w").write(page)
caches.append(cache)

template = environment.get_template("cache_archives.html")
page = template.render(caches=caches)
open(CACHE_PATH / "index.html", "w").write(page)
print("Done: http://larlet.test:8001/david/cache/")


@cli
def new(url):
"""Turn the given URL into a MD and a HTML files.

:url: The URL of the page to put into cache.
"""
hash_url = hashlib.md5(url.encode("utf-8")).hexdigest()
url_cache = f"/david/cache/{hash_url}/"
link_line = f"]({url}) ([cache]({url_cache}))"
print(link_line)
try:
title, content = extract_page(url)
except (lxml.etree.XMLSyntaxError, httpx.exceptions.HTTPError,) as e:
print(f"WARNING: {e}")
title, content = "", ""
cache_path = os.path.join(CACHE_PATH, hash_url)
if not os.path.exists(cache_path):
os.makedirs(cache_path)
# Caching a markdown file.
template = environment.get_template("cache_article.md")
page = template.render(title=title, content=content, url=url, hash_url=hash_url)
result_path = os.path.join(cache_path, "index.md")
open(result_path, "w").write(page)
# Generating the HTML file.
create(hash_url)
md_line = f"> <cite>*[{title}]({url})* ([cache]({url_cache}))</cite>"
print(md_line)
os.popen(f'subl "{result_path}"')
return md_line


@wrap
def perf_wrapper():
start = perf_counter()
yield
elapsed = perf_counter() - start
print(f"Done in {elapsed:.5f} seconds.")


if __name__ == "__main__":
run()

+ 5
- 0
requirements.txt View File

@@ -0,0 +1,5 @@
Jinja2==2.10.3
Markdown==3.1.1
httpx==0.7.8
minicli==0.4.4
readability-lxml==0.7.1

+ 603
- 0
templates/base_2019.html View File

@@ -0,0 +1,603 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang={% block lang %}fr{% endblock lang %}>
<!-- Has to be within the first 1024 bytes, hence before the <title>
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset=utf-8>
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
<!-- Required to make a valid HTML5 document. -->
<title>{% block title %}{% endblock %} — David Larlet</title>
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/static/david/icons/favicon.ico">
<meta name="apple-mobile-web-app-title" content="David Larlet">
<meta name="application-name" content="David Larlet">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
<meta name="theme-color" content="#f0f0ea">
<!-- That good ol' feed, subscribe :p. -->
<link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
{% block extra_head %}{% endblock extra_head %}
<style>
/* http://meyerweb.com/eric/tools/css/reset/ */
html, body, div, span,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, address, big, cite, code,
del, dfn, em, img, ins,
small, strike, strong, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section { display: block; }
body { line-height: 1; }
blockquote, q { quotes: none; }
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

/* http://practicaltypography.com/equity.html */
/* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
/* https://www.filamentgroup.com/lab/js-web-fonts.html */
@font-face {
font-family: 'EquityTextB';
src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'EquityTextB';
src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
font-weight: 300;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'EquityTextB';
src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
{# Restricted to characters:
https://www.fontsquirrel.com/tools/webfont-generator
Protection: Web only
Custom subsetting: ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzéàè!?().:,…«» œôêû-—0123456789ÉÀÊÈÔ’/·%
Em Square Value : 1000
#}
@font-face {
font-family: 'ConcourseT3';
src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
{% if has_code %}
@font-face {
font-family: 'TriplicateT4c';
src: url('/static/david/css/fonts/Triplicate-T4-Code-Regular-webfont.woff2') format('woff2'),
url('/static/david/css/fonts/Triplicate-T4-Code-Regular-webfont.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
{% endif %}

/* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
body {
/* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
font-family: "EquityTextB", Palatino, serif;
background-color: #f0f0ea;
color: #07486c;
font-kerning: normal;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: subpixel-antialiased;
text-rendering: optimizeLegibility;
font-variant-ligatures: common-ligatures contextual;
font-feature-settings: "kern", "liga", "clig", "calt";
}
pre, code, kbd, samp, var, tt {
font-family: 'TriplicateT4c', monospace;
}
em {
font-style: italic;
color: #323a45;
}
strong {
font-weight: bold;
color: black;
}
nav {
background-color: #323a45;
color: #f0f0ea;
display: flex;
justify-content: space-around;
padding: 1rem .5rem;
}
nav:last-child {
border-bottom: 1vh solid #2d7474;
}
nav a {
color: #f0f0ea;
}
nav abbr {
border-bottom: 1px dotted white;
}

h1 {
border-top: 1vh solid #2d7474;
border-bottom: .2vh dotted #2d7474;
background-color: #e3e1e1;
color: #323a45;
text-align: center;
padding: 5rem 0 4rem 0;
width: 100%;
font-family: 'ConcourseT3';
display: flex;
flex-direction: column;
}
h1.single {
padding-bottom: 10rem;
}
h1 span {
position: absolute;
top: 1vh;
left: 20%;
line-height: 0;
}
h1 span a {
line-height: 1.7;
padding: 1rem 1.2rem .6rem 1.2rem;
border-radius: 0 0 6% 6%;
background: #2d7474;
font-size: 1.3rem;
color: white;
text-decoration: none;
}
h2 {
margin: 4rem 0 1rem;
border-top: .2vh solid #2d7474;
padding-top: 1vh;
}
h3 {
text-align: center;
margin: 3rem 0 .75em;
}
hr {
height: .4rem;
width: .4rem;
border-radius: .4rem;
background: #07486c;
margin: 2.5rem auto;
}
time {
display: bloc;
margin-left: 0 !important;
}
ul, ol {
margin: 2rem;
}
ul {
list-style-type: square;
}
a {
text-decoration-skip-ink: auto;
text-decoration-thickness: 0.05em;
text-underline-offset: 0.09em;
}
article {
max-width: 50rem;
display: flex;
flex-direction: column;
margin: 2rem auto;
}
article.single {
border-top: .2vh dotted #2d7474;
margin: -6rem auto 1rem auto;
background: #f0f0ea;
padding: 2rem;
}
article p:last-child {
margin-bottom: 1rem;
}
p {
padding: 0 .5rem;
margin-left: 3rem;
}
p + p,
figure + p {
margin-top: 2rem;
}

blockquote {
background-color: #e3e1e1;
border-left: .5vw solid #2d7474;
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
margin: 1.5rem;
}
blockquote cite {
font-style: italic;
}
blockquote p {
margin-left: 0;
}

figure {
border-top: .2vh solid #2d7474;
background-color: #e3e1e1;
text-align: center;
padding: 1.5rem 0;
margin: 1rem 0 0;
font-size: 1.5rem;
width: 100%;
}
figure img {
max-width: 250px;
max-height: 250px;
border: .5vw solid #323a45;
padding: 1px;
}
figcaption {
padding: 1rem;
line-height: 1.4;
}
aside {
display: flex;
flex-direction: column;
background-color: #e3e1e1;
padding: 1rem 0;
border-bottom: .2vh solid #07486c;
}
aside p {
max-width: 50rem;
margin: 0 auto;
}

/* https://fvsch.com/code/css-locks/ */
p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
font-size: 1rem;
line-height: calc( 1.5em + 0.2 * 1rem );
}
h1 {
font-size: 1.9rem;
line-height: calc( 1.2em + 0.2 * 1rem );
}
h2 {
font-size: 1.6rem;
line-height: calc( 1.3em + 0.2 * 1rem );
}
h3 {
font-size: 1.35rem;
line-height: calc( 1.4em + 0.2 * 1rem );
}
@media (min-width: 20em) {
/* The (100vw - 20rem) / (50 - 20) part
resolves to 0-1rem, depending on the
viewport width (between 20em and 50em). */
p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
margin-left: 0;
}
h1 {
font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
}
h2 {
font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
}
h3 {
font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
}
}
@media (min-width: 50em) {
/* The right part of the addition *must* be a
rem value. In this example we *could* change
the whole declaration to font-size:2.5rem,
but if our baseline value was not expressed
in rem we would have to use calc. */
p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
font-size: calc( 1rem + .6 * 1rem );
line-height: 1.5em;
}
p, li, pre, details {
margin-left: 3rem;
}
h1 {
font-size: calc( 1.9rem + 1.5 * 1rem );
line-height: 1.2em;
}
h2 {
font-size: calc( 1.5rem + 1.5 * 1rem );
line-height: 1.3em;
}
h3 {
font-size: calc( 1.35rem + 1.5 * 1rem );
line-height: 1.4em;
}
figure img {
max-width: 500px;
max-height: 500px;
}
}

figure.unsquared {
margin-bottom: 1.5rem;
}
figure.unsquared img {
height: inherit;
}

{% if has_code %}
/* https://github.com/richleland/pygments-css */
.codehilite{
background-color: #fdf6e3;
margin: 1rem auto;
padding: 1rem;
overflow-x:auto;
box-shadow:inset 0 0 2px rgba(0,0,0,0.2)
}
.codehilite .t{color:#586e75}
.codehilite .w{color:#073642}
.codehilite .err{color:#cb4b16}
.codehilite .k{color:#859900}
.codehilite .kc{color:#2aa198}
.codehilite .kd{color:#268bd2}
.codehilite .kn{color:#b58900}
.codehilite .kp{color:#859900}
.codehilite .kr{color:#073642}
.codehilite .kt{color:#b58900}
.codehilite .n{color:#586e75}
.codehilite .na{color:#2aa198}
.codehilite .nb{color:#268bd2}
.codehilite .nc{color:#268bd2}
.codehilite .ne{color:#cb4b16}
.codehilite .no{color:#2aa198}
.codehilite .nd{color:#2aa198}
.codehilite .ni{color:#2aa198;font-weight:bold}
.codehilite .nf{color:#268bd2}
.codehilite .nn{color:#586e75}
.codehilite .nt{color:#2aa198;font-weight:bold}
.codehilite .nv{color:#cb4b16}
.codehilite .b{color:#859900}
.codehilite .bp{color:#586e75}
.codehilite .v{color:#586e75}
.codehilite .vc{color:#586e75}
.codehilite .vg{color:#268bd2}
.codehilite .vi{color:#268bd2}
.codehilite .m{color:#268bd2}
.codehilite .mf{color:#268bd2}
.codehilite .mh{color:#268bd2}
.codehilite .mi{color:#268bd2}
.codehilite .mo{color:#268bd2}
.codehilite .s{color:#2aa198}
.codehilite .sb{color:#2aa198}
.codehilite .sc{color:#2aa198}
.codehilite .sd{color:#2aa198}
.codehilite .s2{color:#2aa198}
.codehilite .se{color:#cb4b16}
.codehilite .sh{color:#2aa198}
.codehilite .si{color:#cb4b16}
.codehilite .sx{color:#2aa198}
.codehilite .sr{color:#cb4b16}
.codehilite .s1{color:#2aa198}
.codehilite .ss{color:#cb4b16}
.codehilite .il{color:#268bd2}
.codehilite .o{color:#586e75}
.codehilite .ow{color:#859900}
.codehilite .p{color:#586e75}
.codehilite .c{color:#93a1a1;font-style:italic}
.codehilite .cm{color:#93a1a1}
.codehilite .cp{color:#93a1a1}
.codehilite .c1{color:#93a1a1}
.codehilite .cs{color:#93a1a1}
.codehilite .hll{background-color:#dc322f}
.codehilite .g{color:#586e75}
.codehilite .gd{color:#586e75}
.codehilite .ge{font-style:italic}
.codehilite .gr{color:#586e75}
.codehilite .gh{color:#586e75;font-weight:bold}
.codehilite .gi{color:#586e75}
.codehilite .go{color:#586e75}
.codehilite .gp{color:#586e75}
.codehilite .gs{font-weight:bold}
.codehilite .gu{color:#586e75;font-weight:bold}
.codehilite .gt{color:#586e75}
{% endif %}

@media print {
body { font-size: 100%; }
a:after { content: " (" attr(href) ")"; }
a, a:link, a:visited, a:after {
text-decoration: underline;
text-shadow: none !important;
background-image: none !important;
background: white;
color: black;
}
abbr[title] { border-bottom: 0; }
abbr[title]:after { content: " (" attr(title) ")"; }
img { page-break-inside: avoid; }
@page { margin: 2cm .5cm; }
h1, h2, h3 { page-break-after: avoid; }
p3 { orphans: 3; widows: 3; }
img {
max-width: 250px !important;
max-height: 250px !important;
}
nav, aside { display: none; }
}

ul.with_columns {
column-count: 1;
}
@media (min-width: 20em) {
ul.with_columns {
column-count: 2;
}
}
@media (min-width: 50em) {
ul.with_columns {
column-count: 3;
}
}
ul.with_two_columns {
column-count: 1;
}
@media (min-width: 20em) {
ul.with_two_columns {
column-count: 1;
}
}
@media (min-width: 50em) {
ul.with_two_columns {
column-count: 2;
}
}

.gallery {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.gallery figure img {
margin-left: 1rem;
margin-right: 1rem;
}
.gallery figure figcaption {
font-family: 'ConcourseT3'
}

footer {
font-family: 'ConcourseT3';
display: flex;
flex-direction: column;
border-top: 3px solid white;
padding: 4rem 0;
background-color: #07486c;
color: white;
}
footer > * {
max-width: 50rem;
margin: 0 auto;
}
footer a {
color: #f1c40f;
}
footer .avatar {
width: 200px;
height: 200px;
border-radius: 50%;
float: left;
-webkit-shape-outside: circle();
shape-outside: circle();
margin-right: 2rem;
padding: 2px 5px 5px 2px;
background: white;
border-left: 1px solid #f1c40f;
border-top: 1px solid #f1c40f;
border-right: 5px solid #f1c40f;
border-bottom: 5px solid #f1c40f;
}
</style>
{% block content %}{% endblock content %}
{% block nav %}{% endblock nav %}
<footer>
<div>
<img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
<p>
Bonjour/Hi!
Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
</p>
<p>
Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
</p>
{% if note_list %}
<p>
Les dernières publications hebdomadaires sont :
</p>
<ul class="with_columns">
{% for note in note_list %}
<li>
<a href="{{ note.url }}">{{ note.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
<p>
Voici quelques articles choisis :
<a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
<a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
<a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
<a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
<a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
<a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
<a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
<a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
<a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
<a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
<a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
<a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
<a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
<a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
<a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
</p>
<p>
On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
</p>
<p>
Je ne traque pas ta navigation mais mon
<abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
conserve des logs d’accès.
</p>
</div>
</footer>
<script type="text/javascript">
;(_ => {
const jumper = document.getElementById('jumper')
jumper.addEventListener('click', e => {
e.preventDefault()
const anchor = e.target.getAttribute('href')
const targetEl = document.getElementById(anchor.substring(1))
targetEl.scrollIntoView({behavior: 'smooth'})
})
})()
</script>
{% block extrajs %}{% endblock %}

+ 31
- 0
templates/cache_archives.html View File

@@ -0,0 +1,31 @@
{% extends "base_2019.html" %}
{% block title %}Articles archivés{% endblock %}
{% block extra_head %}
<meta name="robots" content="noindex, nofollow">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://larlet.fr/david/cache/">
{% endblock %}
{% block content %}
<h1>
<span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
Articles archivés
<time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
</h1>
<section>
<article>
<ul>
{% for cache in caches %}
<li><a href="/david/cache/{{ cache.hash_url }}/" title="Accès à l'article caché">{{ cache.title }}</a> (<a href="{{ cache.url }}" title="Accès à l'article original">original</a>)</li>
{% endfor %}
</ul>
</article>
</section>
{% endblock content %}
{% block nav %}
<nav id="jumpto">
<p>
<a href="/david/blog/">Accueil du blog</a> |
<a href="/david/stream/2019/">Accueil du flux</a>
</p>
</nav>
{% endblock nav %}

+ 30
- 0
templates/cache_article.html View File

@@ -0,0 +1,30 @@
{% extends "base_2019.html" %}
{% block title %}{{ cache.title }} (archive){% endblock %}
{% block extra_head %}
<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="{{ cache.url }}">
{% endblock %}
{% block content %}
<h1>
<span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
{{ cache.title }} (archive)
<time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
</h1>
<section>
<article>
<h3><a href="{{ cache.url }}">Source originale du contenu</a></h3>
{{ cache.content }}
</article>
</section>
{% endblock content %}
{% block nav %}
<nav id="jumpto">
<p>
<a href="/david/blog/">Accueil du blog</a> |
<a href="{{ cache.url }}">Source originale</a> |
<a href="/david/stream/2019/">Accueil du flux</a>
</p>
</nav>
{% endblock nav %}

+ 5
- 0
templates/cache_article.md View File

@@ -0,0 +1,5 @@
title: {{ title }}
url: {{ url }}
hash_url: {{ hash_url }}

{{ content }}

Loading…
Cancel
Save