Merge pull request #8 from redline6561/content-types

Extensible Content types.
This commit is contained in:
Brit Butler 2013-01-06 12:37:27 -08:00
commit ba4e4d9496
15 changed files with 255 additions and 129 deletions

30
NEWS.md Normal file
View file

@ -0,0 +1,30 @@
## Changes for 0.8 (2013-01-06):
* Add support for new [content types](http://blog.redlinernotes.com/posts/Lessons-from-Coleslaw.html).
* Support for [Multi-site Publishing](http://blub.co.za/posts/Adding-multi-site-support-to-Coleslaw.html).
* CCL and Atom feed bugfixes.
* Major code refactor and docs update.
## Changes for 0.7 (2012-09-20):
* Add commenting support via Disqus plugin.
* Add formal plugin API with per-page predicate support. (aka "injections")
* Note jsmpereira's [coleslaw heroku package](https://github.com/jsmpereira/coleslaw-heroku) in README.
* Support for RSS feeds of arbitrary tags, e.g. "lisp" posts.
## Changes for 0.6.5 (2012-09-12):
* Add support for ATOM feeds.
* Add support for a sitenav in coleslawrc configs.
* Template and rendering cleanup.
* Miscellaneous deployment improvements.
## Changes for 0.6 (2012-08-29):
* Support Markdown in core rather than as a plugin.
* Improve documentation + README.
* Copious bugfixes and code cleanups.
## Changes for 0.5 (2012-08-22):
* Initial release.

10
TODO
View file

@ -4,8 +4,14 @@ BUGS:
; Slugs aren't unicode safe. See [reddit discussion](http://www.reddit.com/r/lisp/comments/yvh6g/coleslaw_jekylllike_static_blogware_in_500_lines/) and [mozilla code](https://github.com/mozilla/unicode-slugify/blob/master/slugify/__init__.py). ; Slugs aren't unicode safe. See [reddit discussion](http://www.reddit.com/r/lisp/comments/yvh6g/coleslaw_jekylllike_static_blogware_in_500_lines/) and [mozilla code](https://github.com/mozilla/unicode-slugify/blob/master/slugify/__init__.py).
TODO: TODO:
; remove need for ordering in header. improve date check/error reporting. -> 0.8 0.9
; doc themes and plugins, s3+hunchentoot. -> 0.8 ; Add SHOUT content type.
;; needs: shout template/render function. Twitter\Disqus integration with shouts?
;; Indices fundamentally don't know about content-types. Is that a problem?
;; Rename index.posts to something else?
Coleslaw.next
; improve date check/error reporting. -> 0.9
; doc themes and plugins, s3+hunchentoot. -> 0.9
; unit tests -> 0.9 ; unit tests -> 0.9
; Incremental compilation: only "touched" posts+tags+months and by-n. -> 1.0 ; Incremental compilation: only "touched" posts+tags+months and by-n. -> 1.0
;; possible plugins: analytics, logging/monitoring, crossposting ;; possible plugins: analytics, logging/monitoring, crossposting

View file

@ -1,7 +1,7 @@
(defsystem #:coleslaw (defsystem #:coleslaw
:name "coleslaw-core" :name "coleslaw-core"
:description "Flexible Lisp Blogware" :description "Flexible Lisp Blogware"
:version "0.7" :version "0.8"
:license "BSD" :license "BSD"
:author "Brit Butler <redline6561@gmail.com>" :author "Brit Butler <redline6561@gmail.com>"
:pathname "src/" :pathname "src/"
@ -12,12 +12,14 @@
:local-time :local-time
:inferior-shell :inferior-shell
:cl-fad :cl-fad
:cl-ppcre) :cl-ppcre
:closer-mop)
:serial t :serial t
:components ((:file "packages") :components ((:file "packages")
(:file "util") (:file "util")
(:file "config") (:file "config")
(:file "themes") (:file "themes")
(:file "content")
(:file "posts") (:file "posts")
(:file "indices") (:file "indices")
(:file "feeds") (:file "feeds")

View file

@ -58,43 +58,86 @@ else
<pre>Homepage: <a href="http://github.com/redline6561/coleslaw">Github</a></pre></div> <pre>Homepage: <a href="http://github.com/redline6561/coleslaw">Github</a></pre></div>
<div class="frame"> <div class="frame">
<div class="labeltitle"> <div class="labeltitle">
<span class="expander" onclick="expand(this, 'classes');">-</span>Classes</div>
<div id="classes">
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="blog_class" href="#blog_class">blog</a>
<span class="lambdalist">(standard-object)</span>
<span class="symboltype">class</span></div>
<div class="documentation">
<pre></pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="content_class" href="#content_class">content</a>
<span class="lambdalist">(standard-object)</span>
<span class="symboltype">class</span></div>
<div class="documentation">
<pre></pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="index_class" href="#index_class">index</a>
<span class="lambdalist">(standard-object)</span>
<span class="symboltype">class</span></div>
<div class="documentation">
<pre></pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="post_class" href="#post_class">post</a>
<span class="lambdalist">(content)</span>
<span class="symboltype">class</span></div>
<div class="documentation">
<pre></pre></div></div></div></div>
<div class="frame">
<div class="labeltitle">
<span class="expander" onclick="expand(this, 'functions');">-</span>Functions</div> <span class="expander" onclick="expand(this, 'functions');">-</span>Functions</div>
<div id="functions"> <div id="functions">
<div class="symboldecl"> <div class="symboldecl">
<div class="definition"> <div class="definition">
<a class="symbolname" name="add-injection_func" href="#add-injection_func">add-injection</a> <a class="symbolname" name="add-injection_func" href="#add-injection_func">add-injection</a>
<span class="lambdalist">str location</span> <span class="lambdalist">injection location</span>
<span class="symboltype">standard-generic-function</span></div> <span class="symboltype">function</span></div>
<div class="documentation"> <div class="documentation">
<pre>Add STR to the list of elements injected in LOCATION.</pre></div></div> <pre>Adds an INJECTION to a given LOCATION for rendering. The INJECTION should be
a string which will always be added or a (string . lambda). In the latter case,
the lambda takes a single argument, a content object, i.e. a POST or INDEX, and
any return value other than nil indicates the injection should be added.</pre></div></div>
<div class="symboldecl"> <div class="symboldecl">
<div class="definition"> <div class="definition">
<a class="symbolname" name="deploy_func" href="#deploy_func">deploy</a> <a class="symbolname" name="discover_func" href="#discover_func">discover</a>
<span class="lambdalist">staging</span> <span class="lambdalist">content-type</span>
<span class="symboltype">standard-generic-function</span></div> <span class="symboltype">standard-generic-function</span></div>
<div class="documentation"> <div class="documentation">
<pre>Deploy the STAGING dir, updating the .prev and .curr symlinks.</pre></div></div> <pre>Load all content of the given CONTENT-TYPE from disk.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="(setf deploy)_func" href="#(setf deploy)_func">(setf deploy)</a>
<span class="lambdalist">new-value object</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>:undocumented</pre></div></div>
<div class="symboldecl"> <div class="symboldecl">
<div class="definition"> <div class="definition">
<a class="symbolname" name="main_func" href="#main_func">main</a> <a class="symbolname" name="main_func" href="#main_func">main</a>
<span class="lambdalist"></span> <span class="lambdalist">config-key</span>
<span class="symboltype">function</span></div> <span class="symboltype">function</span></div>
<div class="documentation"> <div class="documentation">
<pre>Load the user's config, then compile and deploy the blog.</pre></div></div> <pre>Load the user's config section corresponding to CONFIG-KEY, then
compile and deploy the blog.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="page-path_func" href="#page-path_func">page-path</a>
<span class="lambdalist">object</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>The path to store OBJECT at once rendered.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="publish_func" href="#publish_func">publish</a>
<span class="lambdalist">content-type</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>Write pages to disk for all content of the given CONTENT-TYPE.</pre></div></div>
<div class="symboldecl"> <div class="symboldecl">
<div class="definition"> <div class="definition">
<a class="symbolname" name="render_func" href="#render_func">render</a> <a class="symbolname" name="render_func" href="#render_func">render</a>
<span class="lambdalist">content &key next prev &allow-other-keys</span> <span class="lambdalist">object &key next prev &allow-other-keys</span>
<span class="symboltype">standard-generic-function</span></div> <span class="symboltype">standard-generic-function</span></div>
<div class="documentation"> <div class="documentation">
<pre>Render the given CONTENT to HTML.</pre></div></div> <pre>Render the given OBJECT to HTML.</pre></div></div>
<div class="symboldecl"> <div class="symboldecl">
<div class="definition"> <div class="definition">
<a class="symbolname" name="render-content_func" href="#render-content_func">render-content</a> <a class="symbolname" name="render-content_func" href="#render-content_func">render-content</a>

View file

@ -2,9 +2,9 @@
(:use :cl) (:use :cl)
(:export #:enable) (:export #:enable)
(:import-from :coleslaw #:add-injection (:import-from :coleslaw #:add-injection
#:post #:content
#:index #:index
#:post-tags #:content-tags
#:index-posts)) #:index-posts))
(in-package :coleslaw-mathjax) (in-package :coleslaw-mathjax)
@ -21,10 +21,10 @@ src=\"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLor
</script>") </script>")
(defun enable () (defun enable ()
(labels ((math-post-p (post) (labels ((math-post-p (obj)
(member "math" (post-tags post) :test #'string=)) (member "math" (content-tags obj) :test #'string=))
(mathjax-p (content) (mathjax-p (obj)
(etypecase content (etypecase obj
(post (math-post-p content)) (content (math-post-p obj))
(index (some #'math-post-p (index-posts content)))))) (index (some #'math-post-p (index-posts obj))))))
(add-injection (list *mathjax-header* #'mathjax-p) :head))) (add-injection (list *mathjax-header* #'mathjax-p) :head)))

View file

@ -49,7 +49,7 @@ Additional args to render CONTENT can be passed via RENDER-ARGS."
(merge-pathnames "static" (repo *config*)))) (merge-pathnames "static" (repo *config*))))
(when (probe-file dir) (when (probe-file dir)
(run-program "cp -R ~a ." dir))) (run-program "cp -R ~a ." dir)))
(render-posts) (do-ctypes (publish ctype))
(render-indices) (render-indices)
(render-feeds (feeds *config*)))) (render-feeds (feeds *config*))))
@ -76,7 +76,7 @@ Additional args to render CONTENT can be passed via RENDER-ARGS."
"Load the user's config section corresponding to CONFIG-KEY, then "Load the user's config section corresponding to CONFIG-KEY, then
compile and deploy the blog." compile and deploy the blog."
(load-config config-key) (load-config config-key)
(load-posts) (load-content)
(compile-theme (theme *config*)) (compile-theme (theme *config*))
(compile-blog (staging *config*)) (compile-blog (staging *config*))
(deploy (staging *config*))) (deploy (staging *config*)))

84
src/content.lisp Normal file
View file

@ -0,0 +1,84 @@
(in-package :coleslaw)
(defparameter *content* (make-hash-table :test #'equal)
"A hash table to store all the site content and metadata.")
(defclass content ()
((tags :initform nil :initarg :tags :accessor content-tags)
(slug :initform nil :initarg :slug :accessor content-slug)
(date :initform nil :initarg :date :accessor content-date)
(text :initform nil :initarg :text :accessor content-text)))
(defun construct (content-type args)
"Create an instance of CONTENT-TYPE with the given ARGS."
(apply 'make-instance content-type args))
(defgeneric discover (content-type)
(:documentation "Load all content of the given CONTENT-TYPE from disk."))
(defgeneric publish (content-type)
(:documentation "Write pages to disk for all content of the given CONTENT-TYPE."))
(defun read-content (file &optional plist-p)
"Returns two values, a list of metadata from FILE, and the content as a string.
If PLIST-P is non-nil, a single plist is returned with :content holding the text."
(flet ((slurp-remainder (stream)
(let ((seq (make-string (- (file-length stream)
(file-position stream)))))
(read-sequence seq stream)
(remove #\Nul seq)))
(parse-field (str)
(nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+: (.*)" str)))
(field-name (line)
(make-keyword (string-upcase (subseq line 0 (position #\: line)))))
(read-delimited (str &optional (delimiter ", "))
(mapcar #'string-downcase (cl-ppcre:split delimiter str))))
(with-open-file (in file)
(unless (string= (read-line in) ";;;;;")
(error "The provided file lacks the expected header."))
(let ((meta (loop for line = (read-line in nil)
until (string= line ";;;;;")
appending (list (field-name line)
(aref (parse-field line) 0))))
(content (slurp-remainder in)))
(setf (getf meta :tags) (read-delimited (getf meta :tags)))
(if plist-p
(append meta (list :text content))
(values meta content))))))
(defun find-all (content-type)
"Return a list of all instances of a given CONTENT-TYPE."
(loop for val being the hash-values in *content*
when (eql content-type (type-of val)) collect val))
(defun purge-all (content-type)
"Remove all instances of CONTENT-TYPE from *content*."
(dolist (obj (find-all content-type))
(remhash (content-slug obj) *content*)))
(defmacro do-ctypes (&body body)
"Iterate over the subclasses of CONTENT performing BODY with ctype lexically
bound to the current subclass."
(alexandria:with-gensyms (ctypes)
`(let ((,ctypes (closer-mop:class-direct-subclasses (find-class 'content))))
(loop for ctype in (mapcar (compose 'make-keyword 'class-name) ,ctypes)
do ,@body))))
(defun load-content ()
"Load all content stored in the blog's repo."
(do-ctypes (discover ctype)))
(defun by-date (content)
"Sort CONTENT in reverse chronological order."
(sort content #'string> :key #'content-date))
(defun slug-char-p (char)
"Determine if CHAR is a valid slug (i.e. URL) character."
(or (char<= #\0 char #\9)
(char<= #\a char #\z)
(char<= #\A char #\Z)
(member char '(#\_ #\- #\.))))
(defun slugify (string)
"Return a version of STRING suitable for use as a URL."
(remove-if-not #'slug-char-p (substitute #\- #\Space string)))

View file

@ -11,7 +11,7 @@
"Render and write the given FEEDS for the site." "Render and write the given FEEDS for the site."
(flet ((first-10 (list) (flet ((first-10 (list)
(subseq list 0 (min (length list) 10)))) (subseq list 0 (min (length list) 10))))
(let* ((by-date (by-date (hash-table-values *posts*))) (let* ((by-date (by-date (find-all 'post)))
(posts (first-10 by-date)) (posts (first-10 by-date))
(rss (make-instance 'index :id "rss.xml" :posts posts)) (rss (make-instance 'index :id "rss.xml" :posts posts))
(atom (make-instance 'index :id "feed.atom" :posts posts))) (atom (make-instance 'index :id "feed.atom" :posts posts)))

View file

@ -27,62 +27,58 @@
(rel-path (staging *config*) "~d" (index-id object))) (rel-path (staging *config*) "~d" (index-id object)))
(defun all-months () (defun all-months ()
"Retrieve a list of all months with published posts." "Retrieve a list of all months with published content."
(sort (remove-duplicates (mapcar (lambda (x) (get-month (post-date x))) (sort (remove-duplicates (mapcar (lambda (x) (get-month (content-date x)))
(hash-table-values *posts*)) :test #'string=) (hash-table-values *content*)) :test #'string=)
#'string>)) #'string>))
(defun all-tags () (defun all-tags ()
"Retrieve a list of all tags used in posts." "Retrieve a list of all tags used in content."
(sort (remove-duplicates (mappend 'post-tags (hash-table-values *posts*)) (sort (remove-duplicates (mappend 'content-tags (hash-table-values *content*))
:test #'string=) #'string<)) :test #'string=) #'string<))
(defun get-month (timestamp) (defun get-month (timestamp)
"Extract the YYYY-MM portion of TIMESTAMP." "Extract the YYYY-MM portion of TIMESTAMP."
(subseq timestamp 0 7)) (subseq timestamp 0 7))
(defun by-date (posts) (defun index-by-tag (tag content)
"Sort POSTS in reverse chronological order." "Return an index of all CONTENT matching the given TAG."
(sort posts #'string> :key #'post-date)) (let ((results (remove-if-not (lambda (obj) (member tag (content-tags obj)
:test #'string=)) content)))
(defun index-by-tag (tag posts)
"Return an index of all POSTS matching the given TAG."
(let ((content (remove-if-not (lambda (post) (member tag (post-tags post)
:test #'string=)) posts)))
(make-instance 'tag-index :id tag (make-instance 'tag-index :id tag
:posts content :posts results
:title (format nil "Posts tagged ~a" tag)))) :title (format nil "Posts tagged ~a" tag))))
(defun index-by-month (month posts) (defun index-by-month (month content)
"Return an index of all POSTS matching the given MONTH." "Return an index of all CONTENT matching the given MONTH."
(let ((content (remove-if-not (lambda (post) (search month (post-date post))) (let ((results (remove-if-not (lambda (obj) (search month (content-date obj)))
posts))) content)))
(make-instance 'date-index :id month (make-instance 'date-index :id month
:posts content :posts results
:title (format nil "Posts from ~a" month)))) :title (format nil "Posts from ~a" month))))
(defun index-by-n (i posts &optional (step 10)) (defun index-by-n (i content &optional (step 10))
"Return the index for the Ith page of POSTS in reverse chronological order." "Return the index for the Ith page of CONTENT in reverse chronological order."
(make-instance 'int-index :id (1+ i) (make-instance 'int-index :id (1+ i)
:posts (let ((index (* step i))) :posts (let ((index (* step i)))
(subseq posts index (min (length posts) (subseq content index (min (length content)
(+ index step)))) (+ index step))))
:title "Recent Posts")) :title "Recent Posts"))
(defun render-indices () (defun render-indices ()
"Render the indices to view posts in groups of size N, by month, and by tag." "Render the indices to view content in groups of size N, by month, and by tag."
(let ((posts (by-date (hash-table-values *posts*)))) (let ((results (by-date (hash-table-values *content*))))
(dolist (tag (all-tags)) (dolist (tag (all-tags))
(let ((index (index-by-tag tag posts))) (let ((index (index-by-tag tag results)))
(write-page (page-path index) (render-page index)))) (write-page (page-path index) (render-page index))))
(dolist (month (all-months)) (dolist (month (all-months))
(let ((index (index-by-month month posts))) (let ((index (index-by-month month results)))
(write-page (page-path index) (render-page index)))) (write-page (page-path index) (render-page index))))
(dotimes (i (ceiling (length posts) 10)) (dotimes (i (ceiling (length results) 10))
(let ((index (index-by-n i posts))) (let ((index (index-by-n i results)))
(write-page (page-path index) (write-page (page-path index)
(render-page index nil (render-page index nil
:prev (and (plusp i) i) :prev (and (plusp i) i)
:next (and (< (* (1+ i) 10) (length posts)) :next (and (< (* (1+ i) 10) (length results))
(+ 2 i))))))) (+ 2 i)))))))
(update-symlink "index.html" "1.html")) (update-symlink "index.html" "1.html"))

View file

@ -3,12 +3,17 @@
(:use :cl) (:use :cl)
(:import-from :alexandria #:hash-table-values (:import-from :alexandria #:hash-table-values
#:make-keyword #:make-keyword
#:mappend) #:mappend
#:compose)
(:import-from :closure-template #:compile-template) (:import-from :closure-template #:compile-template)
(:export #:main (:export #:main
#:blog #:blog
#:content
#:post #:post
#:index #:index
#:add-injection #:page-path
#:discover
#:publish
#:render
#:render-content #:render-content
#:deploy)) #:add-injection))

View file

@ -1,15 +1,8 @@
(in-package :coleslaw) (in-package :coleslaw)
(defparameter *posts* (make-hash-table :test #'equal) (defclass post (content)
"A hash table to store all the posts and their metadata.") ((title :initform nil :initarg :title :accessor post-title)
(format :initform nil :initarg :format :accessor post-format)))
(defclass post ()
((slug :initform nil :initarg :slug :accessor post-slug)
(title :initform nil :initarg :title :accessor post-title)
(tags :initform nil :initarg :tags :accessor post-tags)
(date :initform nil :initarg :date :accessor post-date)
(format :initform nil :initarg :format :accessor post-format)
(content :initform nil :initarg :content :accessor post-content)))
(defmethod render ((object post) &key prev next) (defmethod render ((object post) &key prev next)
(funcall (theme-fn 'post) (list :config *config* (funcall (theme-fn 'post) (list :config *config*
@ -18,59 +11,26 @@
:next next))) :next next)))
(defmethod page-path ((object post)) (defmethod page-path ((object post))
(rel-path (staging *config*) "posts/~a" (post-slug object))) (rel-path (staging *config*) "posts/~a" (content-slug object)))
(defun read-post (in) (defmethod initialize-instance :after ((object post) &key)
"Make a POST instance based on the data from the stream IN." (with-accessors ((title post-title)
(flet ((check-header () (format post-format)
(unless (string= (read-line in) ";;;;;") (text content-text)) object
(error "The provided file lacks the expected header."))) (setf (content-slug object) (slugify title)
(parse-field (str) format (make-keyword (string-upcase format))
(nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+: (.*)" str))) text (render-content text format))))
(field-name (line)
(subseq line 0 (position #\: line)))
(read-tags (str)
(mapcar #'string-downcase (cl-ppcre:split ", " str)))
(slurp-remainder ()
(let ((seq (make-string (- (file-length in) (file-position in)))))
(read-sequence seq in)
(remove #\Nul seq))))
(check-header)
(let ((args (loop for line = (read-line in nil) until (string= line ";;;;;")
appending (list (make-keyword (string-upcase (field-name line)))
(aref (parse-field line) 0)))))
(setf (getf args :tags) (read-tags (getf args :tags))
(getf args :format) (make-keyword (string-upcase (getf args :format))))
(apply 'make-instance 'post
(append args (list :content (render-content (slurp-remainder)
(getf args :format))
:slug (slugify (getf args :title))))))))
(defun load-posts () (defmethod discover ((content-type (eql :post)))
"Read the stored .post files from the repo." (purge-all 'post)
(clrhash *posts*)
(do-files (file (repo *config*) "post") (do-files (file (repo *config*) "post")
(with-open-file (in file) (let ((post (construct 'post (read-content file t))))
(let ((post (read-post in))) (if (gethash (content-slug post) *content*)
(if (gethash (post-slug post) *posts*) (error "There is already an existing post with the slug ~a."
(error "There is already an existing post with the slug ~a." (content-slug post))
(post-slug post)) (setf (gethash (content-slug post) *content*) post)))))
(setf (gethash (post-slug post) *posts*) post))))))
(defun render-posts () (defmethod publish ((content-type (eql :post)))
"Iterate through the files in the repo to render+write the posts out to disk." (loop for (next post prev) on (append '(nil) (by-date (find-all 'post)))
(loop for (prev post next) on (append '(nil) (sort (hash-table-values *posts*)
#'string< :key #'post-date))
while post do (write-page (page-path post) while post do (write-page (page-path post)
(render-page post nil :prev prev :next next)))) (render-page post nil :prev prev :next next))))
(defun slug-char-p (char)
"Determine if CHAR is a valid slug (i.e. URL) character."
(or (char<= #\0 char #\9)
(char<= #\a char #\z)
(char<= #\A char #\Z)
(member char '(#\_ #\- #\.))))
(defun slugify (string)
"Return a version of STRING suitable for use as a URL."
(remove-if-not #'slug-char-p (substitute #\- #\Space string)))

View file

@ -22,7 +22,7 @@
<name>{$config.author}</name> <name>{$config.author}</name>
<uri>{$config.domain}</uri> <uri>{$config.domain}</uri>
</author> </author>
<content type="html">{$post.content |noAutoescape}</content> <content type="html">{$post.text |noAutoescape}</content>
</entry> </entry>
{/foreach} {/foreach}

View file

@ -6,7 +6,7 @@
<div class="article-meta"> <div class="article-meta">
<a class="article-title" href="{$config.domain}/posts/{$post.slug}.html">{$post.title}</a> <a class="article-title" href="{$config.domain}/posts/{$post.slug}.html">{$post.title}</a>
<div class="date"> posted on {$post.date}</div> <div class="date"> posted on {$post.date}</div>
<div class="article">{$post.content |noAutoescape}</div> <div class="article">{$post.text |noAutoescape}</div>
</div> </div>
{/foreach} {/foreach}
<div id="relative-nav"> <div id="relative-nav">

View file

@ -14,7 +14,7 @@
</div>{\n} </div>{\n}
</div>{\n} </div>{\n}
<div class="article-content">{\n} <div class="article-content">{\n}
{$post.content |noAutoescape} {$post.text |noAutoescape}
</div>{\n} </div>{\n}
<div class="relative-nav">{\n} <div class="relative-nav">{\n}
{if $prev} <a href="{$config.domain}/posts/{$prev.slug}.html">Previous</a><br> {/if}{\n} {if $prev} <a href="{$config.domain}/posts/{$prev.slug}.html">Previous</a><br> {/if}{\n}

View file

@ -20,7 +20,7 @@
{foreach $tag in $post.tags} {foreach $tag in $post.tags}
<category><![CDATA[ {$tag} ]]></category> <category><![CDATA[ {$tag} ]]></category>
{/foreach} {/foreach}
<description><![CDATA[ {$post.content |noAutoescape} ]]></description> <description><![CDATA[ {$post.text |noAutoescape} ]]></description>
</item> </item>
{/foreach} {/foreach}