Skip to content

Commit

Permalink
Implemented prev/next links for content items.
Browse files Browse the repository at this point in the history
  • Loading branch information
svetlyak40wt committed Apr 6, 2024
1 parent dfa4e90 commit 32c7a2b
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 39 deletions.
6 changes: 6 additions & 0 deletions docs/difference-from-coleslaw.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ For templates base on Closure Template, StatiCL defines these filters:
To define additional filters, inherit your template class from CLOSURE-TEMPLATE and define a method for REGISTER-USER-FILTERS generic-function.
Instead of makin 1.html and symlinking to it from index.html, StatiCL just generates first page as index.html and other pages as 2.html, 3.html, etc.
URLs
Templates in Coleslaw used {$site.domain}/ as a prefix to each URL. With StatiCL all URLs are formatted in advance before
variables are passed to the template and you don't have to concatenate string to get a proper URL in a template.
")
1 change: 1 addition & 0 deletions example/.staticlrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:url "https://example.com/"
:pipeline (list (load-content)
(filter (:path "ru/")
(prev-next-links)
(paginated-index :target-path #P"blog"
:page-title-fn (lambda (num)
(fmt "Страница ~A" num)))
Expand Down
63 changes: 54 additions & 9 deletions src/content.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
(:import-from #:staticl/theme
#:template-vars)
(:import-from #:serapeum
#:->
#:dict)
(:import-from #:org.shirakumo.fuzzy-dates)
(:import-from #:staticl/theme
Expand Down Expand Up @@ -30,6 +31,7 @@
#:print-items-mixin
#:print-items)
(:import-from #:closer-mop
#:slot-definition-name
#:class-slots
#:slot-definition-initargs)
(:import-from #:staticl/tag
Expand Down Expand Up @@ -63,7 +65,8 @@
#:content-file
#:content-text
#:content-title
#:content-excerpt-separator))
#:content-excerpt-separator
#:set-metadata))
(in-package #:staticl/content)


Expand Down Expand Up @@ -246,7 +249,7 @@
stage-dir))))


(defmethod object-url ((content content-from-file))
(defmethod object-url ((content content-from-file) &key &allow-other-keys)
(or (slot-value content 'url)
(let* ((root (current-root))
(relative-path (enough-namestring (content-file content)
Expand Down Expand Up @@ -274,13 +277,26 @@
(:documentation "Returns an additional list content objects such as RSS feeds or sitemaps."))


(defmethod template-vars ((content content) &key (hash (dict)))
(setf (gethash "metadata" hash)
(content-metadata content))

(if (next-method-p)
(call-next-method content :hash hash)
(values hash)))
(defmethod template-vars :around ((content content) &key (hash (dict)))
(loop with result = (if (next-method-p)
(call-next-method content :hash hash)
(values hash))
for key being the hash-key of (content-metadata content)
using (hash-value value)
do (setf (gethash key result)
(typecase value
;; For local-time timestamp we want to leave as is
;; because it's formatting may depend on a template.
(local-time:timestamp
value)
;; Here we need transform CLOS objects to hash-tables
;; to make their fields accessable in the template
(standard-object
(template-vars value))
;; Other types are passed as is:
(t
value)))
finally (return result)))


(defmethod content-html ((content content-from-file))
Expand Down Expand Up @@ -350,3 +366,32 @@
(defmethod staticl/pipeline:process-items ((site site) (node load-content) content-items)
(loop for new-item in (read-contents site)
do (staticl/pipeline:produce-item new-item)))


(-> set-metadata (content string t &key (:override-slot boolean))
(values t &optional))

(defun set-metadata (content key value &key override-slot)
"Changes metadata dictionary by adding a new item with key KEY.
Key should be a string and it is automatically downcased.
Note, this way, you can override content's object slots.
To prevent accidential override, function will raise an error
in case if a slot named KEY exists in the object CONTENT.
To force override provide OVERRIDE-SLOT argument."

(unless override-slot
(when (member key
(loop for slot in (class-slots (class-of content))
collect (slot-definition-name slot))
:test #'string-equal)
(cerror "Ignore and override"
"Key ~S already exists as a slot of ~S class.
To override this slot provide :OVERRIDE-SLOT T argument to SET-METADATA function."
key
(class-name (class-of content)))))

(setf (gethash (string-downcase key)
(content-metadata content))
value))
3 changes: 2 additions & 1 deletion src/core.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#:site-content-root
#:site-theme
#:site-plugins
#:site-url
#:make-site)
(:import-from #:staticl/content
#:write-content
Expand Down Expand Up @@ -48,7 +49,7 @@
)

(with-current-root ((site-content-root site))
(with-base-url ((object-url site))
(with-base-url ((site-url site))
(loop for content in all-content
do (write-content site content stage-dir))))

Expand Down
8 changes: 4 additions & 4 deletions src/feeds/base.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@
(defmethod staticl/content:write-content-to-stream ((site site) (feed-file feed-file) stream)
(loop for item in (content-items feed-file)
for feed-entry = (make-instance 'org.shirakumo.feeder:entry
:id (staticl/url:object-url item)
:link (staticl/url:object-url item)
:id (staticl/url:object-url item :full t)
:link (staticl/url:object-url item :full t)
:title (staticl/content:content-title item)
:summary (staticl/content/html-content:content-html-excerpt item)
:content (staticl/content::content-html item))
collect feed-entry into entries
finally
(let* ((feed (make-instance 'org.shirakumo.feeder:feed
:id (staticl/url:object-url site)
:link (staticl/url:object-url site)
:id (staticl/url:object-url site :full t)
:link (staticl/url:object-url site :full t)
:title (staticl/site:site-title site)
:summary (staticl/site:site-description site)
:content entries))
Expand Down
40 changes: 40 additions & 0 deletions src/links/link.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
(uiop:define-package #:staticl/links/link
(:use #:cl)
(:import-from #:staticl/content
#:content-title
#:content)
(:import-from #:serapeum
#:dict
#:dict*
#:->)
(:import-from #:staticl/url
#:object-url)
(:export
#:link))
(in-package #:staticl/links/link)


(defclass link ()
((content :initarg :content
:type content
:reader link-content)))


(-> link (content)
(values link &optional))

(defun link (content)
"Creates a link to the given content piece.
When such object is passed to the template, it is resolved to a
page URL and title."
(make-instance 'link
:content content))


(defmethod staticl/theme:template-vars ((link link) &key (hash (dict)))
(dict* hash
"url"
(object-url (link-content link))
"title"
(content-title (link-content link))))
33 changes: 33 additions & 0 deletions src/links/prev-next.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
(uiop:define-package #:staticl/links/prev-next
(:use #:cl)
(:import-from #:staticl/pipeline)
(:import-from #:serapeum)
(:import-from #:staticl/content
#:set-metadata)
(:import-from #:staticl/site
#:site)
(:import-from #:staticl/links/link
#:link)
(:export #:prev-next-links))
(in-package #:staticl/links/prev-next)


(defclass prev-next-links ()
())


(defun prev-next-links ()
"Creates a links between pages."
(make-instance 'prev-next-links))


(defmethod staticl/pipeline:process-items ((site site) (node prev-next-links) content-items)
(loop for (prev item next) on (list* nil content-items)
when item
do (when prev
(set-metadata item "prev"
(link prev)))
(when next
(set-metadata item "next"
(link next))))
(values))
8 changes: 6 additions & 2 deletions src/site.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
:documentation "Site's charset. By default it is UTF-8.")
(url :initarg :url
:type string
:reader object-url
:reader site-url
:documentation "Site's URL.")
(theme :initarg :theme
:type theme
Expand Down Expand Up @@ -145,8 +145,12 @@
"description"
(site-description site)
"url"
(object-url site)
(site-url site)
"pubdate"
(local-time:now)
"charset"
(site-charset site)))


(defmethod object-url ((site site) &key &allow-other-keys)
(site-url site))
42 changes: 30 additions & 12 deletions src/url.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,39 @@
(defvar *base-url*)


(defgeneric object-url (obj)
(defgeneric object-url (obj &key full)
(:documentation "Returns a full object URL.
A method can return a relative URL, but in this case a call should happen in a context of WITH-BASE-URL macro.")
(:method :around ((obj t))
(let ((result (call-next-method)))
A method should return an relative URL, but if case if FULL argument was given,
the full url with schema and domain will be returned.
Note a call to this method should happen in a context of the WITH-BASE-URL macro,
because it is always return a path from the site's root even if FULL is not given
(in this case return only the path without a domain).
You may wonder: \"Why does we bother to return a path without a domain?\"
It is much easier to service such static site locally for debugging purpose, because
you don't have to setup a web server and dns resolver.
Actually you will need to use FULL argument only in a rare case when you really need
and absolute URL, for example in an RSS feed.")
(:method :around ((obj t) &key full)
(let* ((result (call-next-method))
(absolute-url
(cond
((absolute-url-p result)
(quri:uri result))
(t
(unless (boundp '*base-url*)
(error "Unable to make an absolute URL from ~A because OBJECT-URL was called outside of WITH-BASE-URL."
result))
(quri:merge-uris result
*base-url*)))))
(cond
((absolute-url-p result)
result)
(t
(unless (boundp '*base-url*)
(error "Unable to make an absolute URL from ~A because OBJECT-URL was called outside of WITH-BASE-URL."
result))
(full
(quri:render-uri
(quri:merge-uris result
*base-url*)))))))
absolute-url))
(t
(quri:uri-path absolute-url))))))


(-> absolute-url-p (string)
Expand Down
2 changes: 2 additions & 0 deletions src/user-package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
(:import-from #:serapeum
#:fmt)
;; API imports
(:import-from #:staticl/links/prev-next
#:prev-next-links)
(:import-from #:staticl/plugins/sitemap
#:sitemap)
(:import-from #:staticl/site
Expand Down
6 changes: 3 additions & 3 deletions themes/hyde/base.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</head>
<body>
<div class="navigation">
<a href="{$site.domain}">{$site.title}</a> |
<a href="/">{$site.title}</a> |
{foreach $link in $site.sitenav}
{if $link.relative}
<a href="{$site.domain}/{$link.url}">{$link.name}</a>
Expand All @@ -44,12 +44,12 @@
{$site.license}
{else}
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/deed.en_US">
<img alt="Creative Commons License" style="border-width:0" src="{$site.domain}/css/cc-by-sa.png" />
<img alt="Creative Commons License" style="border-width:0" src="/css/cc-by-sa.png" />
</a>
{/if}
by {$site.author}
<a id="coleslaw-logo" href="https://github.com/redline6561/coleslaw">
<img src="{$site.domain}/css/logo_small.jpg" alt="Coleslaw logo" />
<img src="/css/logo_small.jpg" alt="Coleslaw logo" />
</a>
</div>
</body>
Expand Down
10 changes: 5 additions & 5 deletions themes/hyde/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@
<h1 class="title">{$content.title}</h1>
{foreach $obj in $content.items}
<div class="article-meta">
<a class="article-title" href="{$site.domain}/{$obj.url}">{$obj.title}</a>
<a class="article-title" href="{$obj.url}">{$obj.title}</a>
<div class="date"> posted on {$obj.created_at | date}</div>
<div class="article">{$obj.excerpt |noAutoescape}</div>
</div>
{/foreach}
<div id="relative-nav">
{if $content.prev} <a href="{$site.domain}/{$content.prev.url}">Previous</a> {/if}
{if $content.next} <a href="{$site.domain}/{$content.next.url}">Next</a> {/if}
{if $content.prev} <a href="{$content.prev.url}">Previous</a> {/if}
{if $content.next} <a href="{$content.next.url}">Next</a> {/if}
</div>
{if $content.tags}
<div id="tagsoup">
<p>This blog covers
{foreach $tag in $content.tags}
<a href="{$site.domain}/{$tag.url}">{$tag.name}</a>{nil}
<a href="{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
</div>
Expand All @@ -26,7 +26,7 @@
<div id="monthsoup">
<p>View content from
{foreach $month in $months}
<a href="{$site.domain}/{$month.url}">{$month.name}</a>{nil}
<a href="{$month.url}">{$month.name}</a>{nil}
{if not isLast($month)},{sp}{/if}
{/foreach}
</div>
Expand Down
6 changes: 3 additions & 3 deletions themes/hyde/post.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="tags">{\n}
{if $content.tags}
Tagged as {foreach $tag in $content.tags}
<a href="{$site.domain}/{$tag.url}">{$tag.name}</a>{nil}
<a href="{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
{/if}
Expand All @@ -21,7 +21,7 @@
{$content.html |noAutoescape}
</div>{\n}
<div class="relative-nav">{\n}
{if $prev} <a href="{$site.domain}/{$prev.url}">Previous</a><br> {/if}{\n}
{if $next} <a href="{$site.domain}/{$next.url}">Next</a><br> {/if}{\n}
{if $content.prev} <a href="{$content.prev.url}">Previous</a><br> {/if}{\n}
{if $content.next} <a href="{$content.next.url}">Next</a><br> {/if}{\n}
</div>{\n}
{/template}

0 comments on commit 32c7a2b

Please sign in to comment.