miniblog

Miniblog: A command-line static blog system in Common Lisp
Log | Files | Refs | README | LICENSE

commit 4e91fd2dc72ee6180843012ee2b9778391529ced
parent 3701397bce23d30653cfc5f883c9cf4023b504d6
Author: Decay <decay@todayiwilllaunchmyinfantsonintoorbit.com>
Date:   Tue, 13 Oct 2020 12:52:34 -0700

Switch from cl-emb to Djula

cl-emb is an ugly JSP-like that's pretty outdated at this point. Djula
doesn't have all of Django's features yet but it's in active
development. The new templates are cleaner and we now enforce a
stricter separtion between - aheh - "business logic" and our views.

Diffstat:
Mminiblog.asd | 12+++++-------
Msrc/content.lisp | 159++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/format.lisp | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/packages.lisp | 10++++++++--
Dtemplates/header.lhtml | 5-----
Dtemplates/html-head.lhtml | 32--------------------------------
Dtemplates/left-column.lhtml | 28----------------------------
Dtemplates/nav.lhtml | 36------------------------------------
Atemplates/page.dtl | 18++++++++++++++++++
Dtemplates/pagetemplate.lhtml | 31-------------------------------
Atemplates/posts.dtl | 27+++++++++++++++++++++++++++
Atemplates/rss.dtl | 47+++++++++++++++++++++++++++++++++++++++++++++++
Dtemplates/rss.lxml | 53-----------------------------------------------------
Atemplates/template.dtl | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtemplates/template.lhtml | 45---------------------------------------------
15 files changed, 339 insertions(+), 314 deletions(-)

diff --git a/miniblog.asd b/miniblog.asd @@ -14,7 +14,7 @@ into date-structured directories as a normal HTML." "alexandria" "cl-fad" "cl-markdown" - "cl-emb" + "djula" "dbd-sqlite3" "sxql" "mito" @@ -30,12 +30,10 @@ into date-structured directories as a normal HTML." (:file "content") (:file "miniblog"))) (:module "templates" - :components ((:static-file "html-head.lhtml") - (:static-file "header.lhtml") - (:static-file "left-column.lhtml") - (:static-file "pagetemplate.lhtml") - (:static-file "template.lhtml") - (:static-file "rss.lxml"))) + :components ((:static-file "template.dtl") + (:static-file "posts.dtl") + (:static-file "page.dtl") + (:static-file "rss.dtl"))) (:static-file "COPYING") (:static-file "README")) :in-order-to ((test-op (test-op "miniblog/tests")))) diff --git a/src/content.lisp b/src/content.lisp @@ -3,88 +3,99 @@ (let* ((this-file #.(or *compile-file-truename* *load-truename*)) (src-dir (pathname-directory this-file)) (templates-dir (append (butlast src-dir) '("templates")))) - (defparameter *templates-dir* (make-pathname :directory templates-dir)) - (register-emb "default-template" - (make-pathname :name "template" :type "lhtml" - :directory templates-dir - :defaults this-file)) - (register-emb "default-page-template" - (make-pathname :name "pagetemplate" :type "lhtml" - :directory templates-dir - :defaults this-file)) - (register-emb "rss" - (make-pathname :name "rss" :type "lxml" - :directory templates-dir - :defaults this-file))) + (add-template-directory (make-pathname :directory templates-dir)) + (defvar *posts-template* (compile-template* "posts.dtl")) + (defvar *page-template* (compile-template* "page.dtl")) + (defvar *rss-template* (compile-template* "rss.dtl"))) + +(defun render-posts (posts pages &key stream title root-uri header links + stylesheet year month archive-date-list + enable-rss) + (render-template* *posts-template* stream + :posts posts + :pages pages + :title title + :root-uri root-uri + :header header + :links links + :stylesheet stylesheet + :year year + :month month + :archive-date-list archive-date-list + :enable-rss enable-rss)) + +(defun render-page (page path pages &key stream title root-uri header links + stylesheet year month + archive-date-list enable-rss) + (render-template* *page-template* stream + :page page + :path path + :pages pages + :title title + :root-uri root-uri + :header header + :links links + :stylesheet stylesheet + :year year + :month month + :archive-date-list archive-date-list + :enable-rss enable-rss)) + +(defun render-rss (posts &key stream title link description image-url language + copyright managing-editor webmaster category) + (render-template* *rss-template* stream + :posts posts + :title title + :link link + :description description + :image-url image-url + :language language + :copyright copyright + :managing-editor managing-editor + :webmaster webmaster + :category category + :build-date (miniblog.format:rfc-822-format (now)) )) (defun make-generator (&key title root-uri header links stylesheet) - (lambda (entries pages &key year month archive-date-list enable-rss) - (let ((*default-pathname-defaults* *templates-dir*)) - (execute-emb - "default-template" - :env (list - :title title - :root-uri root-uri - :header header - :links links - :stylesheet stylesheet - :enable-rss enable-rss - :posts entries - :year year - :month month - :pages pages - :archive-date-list archive-date-list - :content-formatter #'miniblog.format:markdown))))) + (lambda (posts pages &key year month archive-date-list enable-rss) + (render-posts posts pages + :title title + :root-uri root-uri + :header header + :links links + :stylesheet stylesheet + :year year + :month month + :archive-date-list archive-date-list + :enable-rss enable-rss))) (defun make-page-generator (&key title root-uri header links stylesheet) - (lambda (entry path pages &key archive-date-list enable-rss) - (let ((*default-pathname-defaults* *templates-dir*)) - (execute-emb - "default-page-template" - :env (list - :title title - :root-uri root-uri - :header header - :links links - :stylesheet stylesheet - :enable-rss enable-rss - :pages pages - :post entry - :path path - :archive-date-list archive-date-list - :content-formatter #'miniblog.format:markdown))))) - -(defun strip-html-tags (content) - (format nil "~{~A~}" - (mapcar - (lambda (s) - (let ((chunks (split ">" s))) - (or (second chunks) (first chunks)))) - (split "<" content)))) + (lambda (page path pages &key archive-date-list enable-rss) + (render-page page path pages + :title title + :root-uri root-uri + :header header + :links links + :stylesheet stylesheet + :enable-rss enable-rss + :archive-date-list archive-date-list))) (defun make-rss-generator (&key title link description image-url language copyright managing-editor webmaster category) (if (and title link description) - (lambda (entries) - (let ((*default-pathname-defaults* *templates-dir*)) - (execute-emb - "rss" - :env (list - :title title - :link link - :description description - :image-url image-url - :language language - :copyright copyright - :managing-editor managing-editor - :webmaster webmaster - :category category - :posts entries - :build-date (miniblog.format:rfc-822-format (now)) - :content-formatter #'miniblog.format:markdown - :content-stripper #'strip-html-tags)))) - (lambda (entries) - (declare (ignore entries))))) + (lambda (posts) + (render-rss posts + :title title + :link link + :description description + :image-url image-url + :language language + :copyright copyright + :managing-editor managing-editor + :webmaster webmaster + :category category)) + (lambda (posts) + (declare (ignore posts))))) (defun get-page-by-path (path pages) (labels ((get-page-name (name page-list) diff --git a/src/format.lisp b/src/format.lisp @@ -11,7 +11,7 @@ Monday, February 3rd 2020 at 2:46 PM PST") (defun markdown (content) - "Translate CONTENT with CL-MARKDOWN" + "Translate CONTENT with cl-markdown" (nth 1 (multiple-value-list (cl-markdown:markdown content :stream nil)))) (defun rfc-822-format (datetime &optional (tz *default-timezone*)) @@ -35,3 +35,69 @@ (defun make-content-formatter () "Return the default content formatter (parses Markdown)" #'markdown) + +;;; Djula formatting elements (tags and filters) + +(def-tag-compiler :page-tree (path pages &optional (root-uri "/")) + (lambda (stream) + (labels ((% (x) + (etypecase x + (string x) + (number x) + (symbol (djula::resolve-variable-phrase (list x))))) + (descend (parent-path child-path pages) + (format stream "<ul class=\"page-list\">~%") + (loop for page in (getf pages :children) + do (let* ((next-name (car child-path)) + (descendents (cdr child-path)) + (page-name (getf page :name)) + (page-path (append parent-path (list page-name))) + (page-path-str (str:join "/" page-path))) + (format stream "<li><a href=\"~apage/~a\">~a</a></li>~%" (% root-uri) page-path-str (getf page :title)) + (if (string= page-name next-name) + (descend page-path descendents page)))) + (format stream "</ul>~%"))) + (descend nil (% path) (% pages))))) + +(def-tag-compiler :nav-calendar (archive-date-list &optional (root-uri "/")) + (flet ((% (x) + (etypecase x + (string x) + (number x) + (symbol (djula::resolve-variable-phrase (list x)))))) + (lambda (stream) + (let ((arc (copy-list (% archive-date-list)))) + (loop while arc do + (format stream "<table class=\"calendar\"><tr><th colspan=\"4\">~a</th></tr>~%" (caar arc)) + (let ((month-entries '()) + (month-names '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))) + (loop for cal-month downfrom 12 to 1 do + (if (and arc (= cal-month (cdar arc))) + (progn + (push (format nil "~d/~2,'0d/index.html" (caar arc) (cdar arc)) month-entries) + (pop arc)) + (push nil month-entries))) + (loop for row from 0 to 2 do + (format stream "<tr>~%") + (loop for cal-month from (* row 4) to (+ (* row 4) 3) do + (format stream "<td>") + (if (nth cal-month month-entries) + (format stream "<a href=\"~a~a\">~a</a>" + (% root-uri) + (nth cal-month month-entries) + (nth cal-month month-names)) + (format stream "~A" (nth cal-month month-names))) + (format stream "</td>~%")) + (format stream "</tr>~%"))) + (format stream "</table>~%")))))) + +(def-filter :markdown (content) + (markdown content)) + +(def-filter :strip-html (content) + (format nil "~{~A~}" + (mapcar + (lambda (s) + (let ((chunks (split ">" s))) + (or (second chunks) (first chunks)))) + (split "<" content)))) diff --git a/src/packages.lisp b/src/packages.lisp @@ -2,6 +2,8 @@ (defpackage :miniblog.format (:use :cl :local-time) + (:import-from :str :split) + (:import-from :djula :def-tag-compiler :def-filter) (:export #:markdown #:rfc-822-format #:short-date-format #:long-date-format #:make-content-formatter #:make-rfc-822-date-formatter @@ -38,8 +40,12 @@ #:move-page)) (defpackage :miniblog.content - (:use :cl :local-time :cl-emb :str) - (:export #:make-generator #:make-page-generator + (:use :cl :local-time :str) + (:import-from :djula :add-template-directory :compile-template* + :render-template*) + (:export *posts-template* *page-template* *rss-template* + #:render-posts #:render-page #:render-rss + #:make-generator #:make-page-generator #:make-rss-generator #:get-archive-date-list #:get-page-id-by-path #:get-page-by-path #:get-path-to-page #:year-month-of-entry #:year-month-of-latest-entry diff --git a/templates/header.lhtml b/templates/header.lhtml @@ -1,5 +0,0 @@ -<header id="miniblog-header"> - <% @if header %> - <% @includevar header %> - <% @endif %> -</header> diff --git a/templates/html-head.lhtml b/templates/html-head.lhtml @@ -1,32 +0,0 @@ -<head> - <title> - <%= (or (getf env :title) "Miniblog") %> - <% @if year %> - - <% @var year %>/<% @var month %> - <% @endif %> - </title> - <style> - header#miniblog-header { width: 100%; } - section#miniblog-left { float: left; width: 15%; } - section#miniblog-main { float: left; width: <%= (if (getf env :links) "65%" "80%") %>; } - nav#miniblog-nav { float: left; width: 20%; } - div#miniblog-rss { clear: both; } - table.calendar { padding: 10px; float: left; } - table.calendar td { width: 25%; } - ul.page-list { list-style: none; margin: 0; padding: 0 0 0 10px; } - @media screen and (max-aspect-ratio: 1/1) { - section#miniblog-left { float: none; width: 100%; } - section#miniblog-main { float: none; width: 100%; } - nav#miniblog-nav { float: none; width: 100%; } - } - </style> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <% @if stylesheet %> - <link rel="stylesheet" type="text/css" href="<% @var stylesheet %>"> - <% @endif %> - <% @if enable-rss %> - <link rel="alternate" type="application/rss+xml" - title="RSS feed for <%= (or (getf env :title) "Miniblog") %>" - href="<% @var root-uri %>rss.xml"> - <% @endif %> -</head> diff --git a/templates/left-column.lhtml b/templates/left-column.lhtml @@ -1,28 +0,0 @@ -<section id="miniblog-left"> - <a href="/">Home</a><br> - <% @if pages %><% - (labels ((descend (parent-path child-path pages) - (format t "<ul class=\"page-list\">~%") - (loop for page in (getf pages :children) - do (let* ((next-name (car child-path)) - (descendents (cdr child-path)) - (page-name (getf page :name)) - (page-path (append parent-path (list page-name))) - (page-path-str (str:join "/" page-path))) - (format t "<li><a href=\"~apage/~a\">~a</a></li>~%" (getf env :root-uri) page-path-str (getf page :title)) - (if (string= page-name next-name) - (descend page-path descendents page)))) - (format t "</ul>~%"))) - (descend nil (getf env :path) (getf env :pages))) - %><% @endif %> - <br> - <% @if links %> - <% @loop links %> - <% @if link %> - <a href="<% @var link %>"><% @var text %></a><br> - <% @else %> - <br> - <% @endif %> - <% @endloop %> - <% @endif %> -</section> diff --git a/templates/nav.lhtml b/templates/nav.lhtml @@ -1,36 +0,0 @@ -<nav id="miniblog-nav"> - <% @if archive-date-list %> - <% - (let ((arc (copy-list (getf env :archive-date-list)))) - (loop while arc do - (format t "<table class=\"calendar\"><tr><th colspan=\"4\">~a</th></tr>~%" (caar arc)) - (let ((month-entries '()) - (month-names '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))) - (loop for cal-month downfrom 12 to 1 do - (if (and arc (= cal-month (cdar arc))) - (progn - (push (format nil "~d/~2,'0d/index.html" (caar arc) (cdar arc)) month-entries) - (pop arc)) - (push nil month-entries))) - (loop for row from 0 to 2 do - (format t "<tr>~%") - (loop for cal-month from (* row 4) to (+ (* row 4) 3) do - (format t "<td>") - (if (nth cal-month month-entries) - (format t "<a href=\"~a~a\">~a</a>" - (or (getf env :root-uri) "") - (nth cal-month month-entries) - (nth cal-month month-names)) - (format t "~A" (nth cal-month month-names))) - (format t "</td>~%")) - (format t "</tr>~%"))) - (format t "</table>~%"))) %> - <% @endif %> - <% @if enable-rss %> - <div id="miniblog-rss"> - <a href="<% @var root-uri %>rss.xml" target="_blank"> - Subscribe to <%= (or (getf env :title) "Miniblog") %> - </a> - </div> - <% @endif %> -</nav> diff --git a/templates/page.dtl b/templates/page.dtl @@ -0,0 +1,18 @@ +{% extends "template.dtl" %} +{% block main %} + {% if page.content %} + <h2>{{ page.title }}</h2> + <article>{{ page.content|safe|markdown }}</article> + <p> + <small> + Posted by {{ page.created-by }} on {{ page.created-at-long }} + {% ifnotequal page.created-at-long page.last-updated-at-long %} + <br> + Last updated by {{ page.last-updated-by }} on {{ page.last-updated-at-long }} + {% endifnotequal %} + </small> + </p> + {% else %} + No page found. + {% endif %} +{% endblock %} diff --git a/templates/pagetemplate.lhtml b/templates/pagetemplate.lhtml @@ -1,31 +0,0 @@ -<!DOCTYPE html> -<html> - <% @include html-head.lhtml %> - <body> - <% @include header.lhtml %> - <% @include left-column.lhtml %> - <section id="miniblog-main"> - <% @if post/content %> - <% (destructuring-bind (&key ((:post (&key content created-at last-updated-at created-at-long last-updated-at-long last-updated-by &allow-other-keys))) content-formatter &allow-other-keys) env %> - <h2><% @var post/title %></h2> - <article> - <%= (funcall content-formatter content) %> - </article> - <p> - <small> - Posted by <% @var post/created-by %> on - <% @var post/created-at-long %> - <% (if (local-time:timestamp/= created-at last-updated-at) - (format t "<br>~%Last updated by ~A on ~A~%" - last-updated-by - last-updated-at-long)) %> - </small> - </p> - <% ) %> - <% @else %> - No page found. - <% @endif %> - </section> - <% @include nav.lhtml %> - </body> -</html> diff --git a/templates/posts.dtl b/templates/posts.dtl @@ -0,0 +1,27 @@ +{% extends "template.dtl" %} +{% block main %} + {% if posts %} + {% for post in posts %} + {% if not forloop.first %} + <hr> + {% endif %} + {% ifchanged post.created-at-short %} + <h1>{{ post.created-at-short }}</h1> + {% endifchanged %} + <a name="{{ post.id }}"></a> + <h2>{{ post.title }}</h2> + <article>{{ post.content|safe|markdown }}</article> + <p> + <small> + Posted by {{ post.created-by }} on {{ post.created-at-long }} + {% ifnotequal post.created-at-long post.last-updated-at-long %} + <br> + Last updated by {{ post.last-updated-by }} on {{ post.last-updated-at-long }} + {% endifnotequal %} + </small> + </p> + {% endfor %} + {% else %} + No posts found. + {% endif %} +{% endblock %} diff --git a/templates/rss.dtl b/templates/rss.dtl @@ -0,0 +1,47 @@ +<rss version="2.0"> + <channel> + <title>{{ title }}</title> + <link>{{ link }}</link> + <description>{{ description }}</description> + <generator>Miniblog</generator> + <docs>https://validator.w3.org/feed/docs/rss2.html</docs> + <lastBuildDate> + {{ build-date }} + </lastBuildDate> + {% if image-url %} + <image> + <url>{{ image-url }}</url> + <title>{{ title }}</title> + <link>{{ link }}</link> + </image> + {% endif %} + {% if language %} + <language>{{ language }}</language> + {% endif %} + {% if copyright %} + <copyright>{{ copyright }}</copyright> + {% endif %} + {% if managing-editor %} + <managingEditor>{{ managing-editor }}</managingEditor> + {% endif %} + {% if webmaster %} + <webmaster>{{ webmaster }}</webmaster> + {% endif %} + {% if category %} + <category>{{ category }}</category> + {% endif %} + {% if posts %} + <pubDate> + {{ posts.0.created-at-rfc-822 }} + </pubDate> + {% for post in posts %} + <item> + <title>{{ post.title }}</title> + <link>{{ link }}#{{ post.id }}</link> + <description>{{ post.content|markdown|strip-html|truncatechars:200 }}</description> + <pubDate>{{ post.last-updated-at-rfc-822 }}</pubDate> + </item> + {% endfor %} + {% endif %} + </channel> +</rss> diff --git a/templates/rss.lxml b/templates/rss.lxml @@ -1,53 +0,0 @@ -<rss version="2.0"> - <channel> - <title><% @var title %></title> - <link><% @var link %></link> - <description><% @var description %></description> - <generator>Miniblog</generator> - <docs>https://validator.w3.org/feed/docs/rss2.html</docs> - <lastBuildDate> - <% @var build-date %> - </lastBuildDate> - <% @if image-url %> - <image> - <url><% @var image-url %></url> - <title><% @var title %></title> - <link><% @var link %></link> - </image> - <% @endif %> - <% @if language %> - <language><% @var language %></language> - <% @endif %> - <% @if copyright %> - <copyright><% @var copyright %></copyright> - <% @endif %> - <% @if managing-editor %> - <managingEditor><% @var managing-editor %></managingEditor> - <% @endif %> - <% @if webmaster %> - <webmaster><% @var webmaster %></webmaster> - <% @endif %> - <% @if category %> - <category><% @var category %></category> - <% @endif %> - <% @if posts %> - <pubDate> - <% (let* ((latest (first (getf env :posts))) (pub-date (getf latest :last-updated-at-rfc-822))) %> - <%= pub-date %> - <% ) %> - </pubDate> - <% (loop for post in (getf env :posts) do %> - <% (destructuring-bind (&key id title content last-updated-at &allow-other-keys) post %> - <item> - <% (let* ((formatted-content (funcall (getf env :content-formatter) content)) (stripped-content (funcall (getf env :content-stripper) formatted-content)) (truncated-content (str:substring 0 200 stripped-content))) %> - <title><%= title %></title> - <link><% @var link %>#<%= id %></link> - <description><%= (if (equal stripped-content truncated-content) stripped-content (concatenate 'string truncated-content "...")) %></description> - <pubDate><% @var last-updated-at-rfc-822 %></pubDate> - <% ) %> - </item> - <% ) %> - <% ) %> - <% @endif %> - </channel> -</rss> diff --git a/templates/template.dtl b/templates/template.dtl @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<html> + <head> + {% block html-head %} + <title> + {{ title }} + {% if year %} + - {{ year }}/{{ month }} + {% endif %} + </title> + <style> + header#miniblog-header { width: 100%; } + section#miniblog-left { float: left; width: 15%; } + section#miniblog-main { float: left; width: {% if links %}65%{% else %}80%{% endif %}; } + nav#miniblog-nav { float: left; width: 20%; } + div#miniblog-rss { clear: both; } + table.calendar { padding: 10px; float: left; } + table.calendar td { width: 25%; } + ul.page-list { list-style: none; margin: 0; padding: 0 0 0 10px; } + @media screen and (max-aspect-ratio: 1/1) { + section#miniblog-left { float: none; width: 100%; } + section#miniblog-main { float: none; width: 100%; } + nav#miniblog-nav { float: none; width: 100%; } + } + </style> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + {% if stylesheet %} + <link rel="stylesheet" type="text/css" href="{{ stylesheet }}"> + {% endif %} + {% if enable-rss %} + <link rel="alternate" type="application/rss+xml" + title="RSS feed for {{ title }}" + href="{{ root-uri }}rss.xml"> + {% endif %} + {% endblock %} + </head> + <body> + <header id="miniblog-header"> + {% block header %} + {% if header %} + {% include header %} + {% endif %} + {% endblock %} + </header> + <section id="miniblog-left"> + {% block left %} + <a href="/">Home</a><br> + {% if pages %} + {% page-tree path pages root-uri %} + {% endif %} + <br> + {% if links %} + {% for link in links %} + {% if link.link %} + <a href="{{ link.link }}">{{ link.text }}</a><br> + {% else %} + <br> + {% endif %} + {% endfor %} + {% endif %} + {% endblock %} + </section> + <section id="miniblog-main"> + {% block main %} + {% endblock %} + </section> + <nav id="miniblog-nav"> + {% block nav %} + {% if archive-date-list %} + {% nav-calendar archive-date-list root-uri %} + {% endif %} + {% if enable-rss %} + <div id="miniblog-rss"> + <a href="{{ root-uri }}rss.xml" target="_blank"> + Subscribe to {{ title }} + </a> + </div> + {% endif %} + {% endblock %} + </nav> + </body> +</html> diff --git a/templates/template.lhtml b/templates/template.lhtml @@ -1,45 +0,0 @@ -<!DOCTYPE html> -<html> - <% @include html-head.lhtml %> - <body> - <% @include header.lhtml %> - <% @include left-column.lhtml %> - <section id="miniblog-main"> - <% @if posts %> - <% (let ((short-date) (render-hr nil)) %> - <% (loop for post in (getf env :posts) do %> - <% (destructuring-bind (&key id created-at created-at-short created-at-long last-updated-at last-updated-at-long title content created-by last-updated-by &allow-other-keys) post %> - <% (let ((curr-short-date created-at-short)) %> - <% (if render-hr %> - <hr> - <% ) %> - <% (setf render-hr t) %> - <% (if (string/= short-date curr-short-date) - (progn - (setf short-date curr-short-date) - (format t "<h1>~A</h1>~%" short-date)))) %> - <a name="<%= id %>"></a> - <h2><%= title %></h2> - <article> - <%= (funcall (getf env :content-formatter) content) %> - </article> - <p> - <small> - Posted by <%= created-by %> on - <%= created-at-long %> - <% (if (local-time:timestamp/= created-at last-updated-at) - (format t "<br>~%Last updated by ~A on ~A~%" - last-updated-by - last-updated-at-long)) %> - </small> - </p> - <% ) %> - <% ) %> - <% ) %> - <% @else %> - No posts found. - <% @endif %> - </section> - <% @include nav.lhtml %> - </body> -</html>