src/templates/blog/index.html \
src/templates/blog/post.html \
src/templates/blog/search-results.html \
- src/templates/blog/tag.html
+ src/templates/blog/tag.html \
+ src/templates/blog/year.html
templates_blogdir = $(templatesdir)/blog
templates_blog_modules_DATA = \
+ src/templates/blog/modules/history-navigation.html \
+ src/templates/blog/modules/list.html \
src/templates/blog/modules/post.html \
src/templates/blog/modules/posts.html
#!/usr/bin/python
+import textile
+
from . import misc
class Blog(misc.Object):
AND published_at <= NOW() \
ORDER BY published_at DESC LIMIT %s", uid, limit)
+ def get_by_year(self, year):
+ return self._get_posts("SELECT * FROM blog \
+ WHERE EXTRACT(year FROM published_at) = %s \
+ AND published_at IS NOT NULL \
+ AND published_at <= NOW() \
+ ORDER BY published_at DESC", year)
+
def search(self, query, limit=None):
return self._get_posts("SELECT blog.* FROM blog \
LEFT JOIN blog_search_index search_index ON blog.id = search_index.post_id \
"""
self.db.execute("REFRESH MATERIALIZED VIEW blog_search_index")
+ @property
+ def years(self):
+ res = self.db.query("SELECT DISTINCT EXTRACT(year FROM published_at)::integer AS year \
+ FROM blog WHERE published_at IS NOT NULL AND published_at <= NOW() \
+ ORDER BY year DESC")
+
+ for row in res:
+ yield row.year
+
class Post(misc.Object):
def init(self, id, data=None):
def published_at(self):
return self.data.published_at
+ @property
+ def markdown(self):
+ return self.data.markdown
+
@property
def html(self):
"""
Returns this post as rendered HTML
"""
- return self.data.html
+ return self.data.html or textile.textile(self.markdown.decode("utf-8"))
@property
def tags(self):
<div class="card-body">
<h5>{{ _("%s's posts") % author.name }}</h5>
- {% for post in posts %}
- <strong class="mb-0">
- <a href="/post/{{ post.slug }}">{{ post.title }}</a>
- </strong>
- <p class="text-muted small">
- {{ locale.format_date(post.published_at, shorter=True, relative=False) }}
- </p>
- {% end %}
+ {% module BlogList(posts) %}
</div>
</div>
{% end block %}
</li>
</ul>
</nav>
+
+ {% module BlogHistoryNavigation() %}
</div>
<div class="col">
--- /dev/null
+<p class="small text-uppercase text-muted ml-3">{{ _("History") }}</p>
+
+<nav class="mb-5">
+ <ul class="nav flex-column">
+ {% for year in years %}
+ <li class="nav-item">
+ <a class="nav-link {% if request.path == "/years/{{ year }}" %}active{% end %}" href="/years/{{ year }}">
+ {{ year }}
+ </a>
+ </li>
+ {% end %}
+ </ul>
+</nav>
--- /dev/null
+{% for post in posts %}
+ <strong class="mb-0">
+ <a href="/post/{{ post.slug }}">{{ post.title }}</a>
+ </strong>
+ <p class="text-muted small">
+ {{ locale.format_date(post.published_at, shorter=True, relative=False) }}
+ </p>
+{% end %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Posts in %s") % year }}{% end block %}
+
+{% block main %}
+ <div class="card">
+ <div class="card-body">
+ <h5>{{ _("Posts in %s") % year }}</h5>
+
+ {% module BlogList(posts) %}
+ </div>
+ </div>
+{% end block %}
"format_month_name" : self.format_month_name,
},
"ui_modules" : {
+ "BlogHistoryNavigation": blog.HistoryNavigationModule,
+ "BlogList" : blog.ListModule,
"BlogPost" : blog.PostModule,
"BlogPosts" : blog.PostsModule,
(r"/post/(.*)", blog.PostHandler),
(r"/search", blog.SearchHandler),
(r"/tags/([0-9a-z\-]+)", blog.TagHandler),
+ (r"/years/([0-9]+)", blog.YearHandler),
# RSS Feed
(r"/feed.xml", blog.FeedHandler),
self.render("blog/tag.html", posts=posts, tag=tag)
+class YearHandler(base.BaseHandler):
+ def get(self, year):
+ posts = self.backend.blog.get_by_year(year)
+ if not posts:
+ raise tornado.web.HTTPError(404, "There are no posts in %s" % year)
+
+ self.render("blog/year.html", posts=posts, year=year)
+
+
+class HistoryNavigationModule(ui_modules.UIModule):
+ def render(self):
+ return self.render_string("blog/modules/history-navigation.html",
+ years=self.backend.blog.years)
+
+
+class ListModule(ui_modules.UIModule):
+ def render(self, posts):
+ return self.render_string("blog/modules/list.html", posts=posts)
+
+
class PostModule(ui_modules.UIModule):
def render(self, post):
return self.render_string("blog/modules/post.html", post=post)
from .. import database
class UIModule(tornado.web.UIModule):
+ @property
+ def backend(self):
+ return self.handler.backend
+
@property
def accounts(self):
return self.handler.accounts