├── LICENSE
├── README.org
└── org-static-blog.el
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015, Bastian Bechtold
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the
13 | distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived
17 | from this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+TITLE: ORG-STATIC-BLOG
2 |
3 | [[http://melpa.org/packages/org-static-blog-badge.svg]] [[http://stable.melpa.org/packages/org-static-blog-badge.svg]]
4 |
5 |
6 | Static blog generators are a dime a dozen. This is one more, which
7 | focuses on being simple. All files are simple org-mode files in a
8 | directory. The only requirement is that every org file must have a
9 | =#+TITLE= and a =#+DATE=, and optionally, =#+FILETAGS=, =#+DESCRIPTION= and
10 | =#+IMAGE=.
11 |
12 | =#+FILETAGS= set the tags for the post, which can be delimited by
13 | colons in the form of =#+FILETAGS: :foo bar:baz:=, or by whitespaces
14 | in the form of =#+FILETAGS: foo bar=. And you can put a short summary
15 | in =#+DESCRIPTION=, which will be converted to a HTML
16 | ~~ tag, it's good for SEO as described in
17 | Wikipedia's [[https://en.wikipedia.org/wiki/Meta_element#The_description_attribute][Meta element]].
18 |
19 | If =org-static-blog-enable-og-tags= is set to =t=, =#+DESCRIPTION= and
20 | =#+IMAGE= (it must be e a relative url) will generate the =og:description=
21 | and =og:image= meta properties that will be used in URL previews
22 | generated in social networks and similar sites (see [[https://ogp.me/][Open Graph
23 | Protocol]]).
24 |
25 | This file is also available from marmalade and melpa-stable.
26 |
27 | Set up your blog by customizing org-static-blog's parameters, then
28 | call =M-x org-static-blog-publish= to render the whole blog or
29 | =M-x org-static-blog-publish-file filename.org= to render only only
30 | the file =filename.org=.
31 |
32 | Above all, I tried to make org-static-blog as simple as possible.
33 | There are no magic tricks, and all of the source code is meant to be
34 | easy to read, understand and modify.
35 |
36 | For org-static-blog, a blog consists of six parts:
37 | - Blog posts contain individual entries. Every org file in
38 | =org-static-blog-posts-directory= is one blog post. Each blog post
39 | is rendered as its own HTML page.
40 | - The index page contains the last few blog posts on a single page.
41 | The number of entries on the index page can be customized using
42 | =org-static-blog-index-length=.
43 | - Optionally show a preview of the post (instead of the full post) on
44 | the index page setting =org-static-blog-use-preview= to t. The region
45 | of the post used as a preview is, by default, its first paragraph,
46 | but can be fine-tuned using =org-static-blog-preview-start= and
47 | =org-static-blog-preview-end.=
48 | - The archive page lists the publishing dates and headlines of every
49 | blog post.
50 | - The RSS feed is a machine-readable XML file that contains every blog
51 | post. It is not meant to be consumed by humans. Instead RSS readers
52 | can use the RSS feed to aggregate entries from multiple blogs.
53 | - Drafts are rendered like regular blog posts, but are not included in
54 | the index, the archive, or the RSS feed.
55 | - Each blog post can be tagged, and each tag links to a page that
56 | lists all other posts of the same tag. Additionally, a tag overview
57 | page is created that lists the publishing dates and headlines of
58 | every blog post, sorted by tags. This feature is only enabled if you
59 | set =org-static-blog-enable-tags= to =t=.
60 | - If =org-static-blog-enable-og-tags= is set to =t=, all generated pages
61 | will include some useful [[https://ogp.me/][Open Graph]] meta properties such as the
62 | title, the description and an optional image. If no =#+IMAGE= property
63 | is provided in a post, the default one specified in
64 | =org-static-blog-image= will be used.
65 | - To disable comments for single blog posts, reserve a tag name as
66 | =org-static-blog-no-comments-tag= and tag the post with that tag.
67 | - It is also possible to create per-tag RSS feeds. This feature is
68 | only enabled when you set =org-static-blog-enable-tag-rss= to t.
69 |
70 | Every HTML page in org-static-blog can be customized in the following
71 | ways:
72 | - The contents of =org-static-blog-page-header= are inserted into the
73 | =
= of every page. Use this to include custom CSS and
74 | JavaScript for your blog.
75 | - The contents of =org-static-blog-page-preamble= is inserted just
76 | before the content of every page. This is a good place to put the
77 | header or menus for your blog.
78 | - The contents of =org-static-blog-page-postamble= is inserted after
79 | the content of every generated page: after any blog post page, after
80 | the index page, the tag pages and the archive. This is where you can
81 | include copyright notices.
82 | - The return values of =org-static-blog-post-preamble= and
83 | =org-static-blog-post-postamble= are prepended and appended to every
84 | blog post. If you want to change the formatting of dates, titles, or
85 | the tag list, overwrite these functions. In particular the content
86 | of =org-static-blog-post-comments= is inserted at the end of each
87 | blog post. Use this to add a comment box.
88 |
89 | You can customize the RSS feed output by setting
90 | =org-static-blog-rss-extra=. Its content is placed right before the
91 | sequence of posts. For example you can add an RSS icon for the feed,
92 | or advertise that you built your blog with org-static-blog. You can
93 | also limit the number of entries in the feed via
94 | =org-static-blog-rss-max-entries=.
95 |
96 |
97 | There are some static texts like "/Other posts/", "/Tags/" etc that
98 | org-static-blog includes in produced html. By default org-static-blog
99 | uses english texts, but language chosen depends on value set to
100 | =org-static-blog-langcode=. If your language is not supported yet, you
101 | will see placeholders like =[other-posts:de]= and =[tags:de]=.
102 | You can add new language by adding texts to =org-static-blog-texts=
103 | list. And if you do, please share and create Pull Request.
104 |
105 | If you want to activate a few convenience key bindings, add
106 | =(add-to-list 'auto-mode-alist (cons (concat org-static-blog-posts-directory ".*\\.org\\'") 'org-static-blog-mode))=
107 | to your /init.el/. These key bindings are:
108 | - =C-c C-f= / =C-c C-b= to open next/previous post.
109 | - =C-c C-p= to open the matching published HTML file of a post.
110 | - =C-c C-n= to create a new blog post.
111 |
112 |
113 | If you have questions, if you find bugs, or if you would like to
114 | contribute something to org-static-blog, please open an issue or pull
115 | request on GitHub.
116 |
117 | Finally, I would like to remind you that I am developing this project
118 | for free, and in my spare time. While I try to be as accommodating as
119 | possible, I can not guarantee a timely response to issues. Publishing
120 | Open Source Software on GitHub does not imply an obligation to /fix
121 | your problem right now/. Please be civil.
122 |
123 | * Examples
124 |
125 | ** Minimal Configuration
126 | This minimal configuration should be added to your /init.el/, and will
127 | set up a minimal org-static-blog for the URL https://staticblog.org,
128 | which will be saved in the directory ~/projects/blog/.
129 |
130 | #+begin_src elisp
131 | (setq org-static-blog-publish-title "My Static Org Blog")
132 | (setq org-static-blog-publish-url "https://staticblog.org/")
133 | (setq org-static-blog-publish-directory "~/projects/blog/")
134 | (setq org-static-blog-posts-directory "~/projects/blog/posts/")
135 | (setq org-static-blog-drafts-directory "~/projects/blog/drafts/")
136 | (setq org-static-blog-enable-tags t)
137 | (setq org-export-with-toc nil)
138 | (setq org-export-with-section-numbers nil)
139 |
140 | ;; This header is inserted into the section of every page:
141 | ;; (you will need to create the style sheet at
142 | ;; ~/projects/blog/static/style.css
143 | ;; and the favicon at
144 | ;; ~/projects/blog/static/favicon.ico)
145 | (setq org-static-blog-page-header
146 | "
147 |
148 |
149 |
150 | ")
151 |
152 | ;; This preamble is inserted at the beginning of the of every page:
153 | ;; This particular HTML creates a
with a simple linked headline
154 | (setq org-static-blog-page-preamble
155 | "
")
167 |
168 | ;; This HTML code is inserted into the index page between the preamble and
169 | ;; the blog posts
170 | (setq org-static-blog-index-front-matter
171 | "
Welcome to my blog
\n")
172 | #+end_src
173 |
174 | In order for this to work, you will also need to create a style sheet
175 | at /~/projects/blog/static/style.css/, which might for example change
176 | the appearance of the ~#preamble~, the ~#content~, and the
177 | ~#postamble~.
178 |
179 | To write posts, you can now call ~org-static-blog-create-new-post~,
180 | and render your blog with ~org-static-blog-publish~.
181 |
182 | Each post is an org-mode file such as
183 |
184 | #+begin_src org-mode
185 | #+title: How to Write a Blog Post
186 | #+date: <2020-07-03 08:57>
187 | #+filetags: computers emacs blog
188 |
189 | Step one: Install ~org-static-blog~. \\
190 | Step Two: Execute ~M-x org-static-blog-create-new-post~ and write the content. \\
191 | Step Three: Execute ~M-x org-static-blog-publish~ and upload to your webhost. \\
192 | Done.
193 | #+end_src
194 |
195 | You can find more complete examples by looking at my [[https://github.com/bastibe/.emacs.d/blob/master/init.el#L670][init.el]] and the
196 | [[https://github.com/bastibe/bastibe.github.com][repository]] for my blog ([[http://bastibe.de/][bastibe.de]]) itself to see an example of how to
197 | use =org-static-blog= in practice.
198 |
199 | *** Other org-static-blog blogs:
200 | - [[https://zngguvnf.org/][zngguvnf.org]] ---see the [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][writeup]]
201 | - [[https://matthewbauer.us/blog/][matthewbauer.us/blog/]]
202 | - [[https://jao.io/blog/simplicity-pays-off.html][jao's programming musings]]
203 | - [[https://f-santos.gitlab.io/][f-santos.gitlab.io]]
204 | - [[https://xgqt.gitlab.io/blog/][xgqt.gitlab.io/blog]]
205 | - [[https://unmonoqueteclea.github.io/][unmonoqueteclea]]
206 | - [[https://chenyo-17.github.io/org-static-blog/][chenyo-17.github.io/org-static-blog]]
207 | - [[https://dou-meishi.github.io/org-blog/][doumeishi.github.io/org-blog]] --- see the [[https://dou-meishi.github.io/org-blog/2024-01-22-TryOrgStaticBlog/notes.html][writeup]]
208 | - Please open a pull request to add your blog, here!
209 |
210 | ** Features
211 | *** Hide some subtrees when publishing
212 | - Background
213 | When publishing some posts, we may not want to publish the more private or unfinished parts of the subtrees. So maybe we can use tags to identify these subtrees and ignore them during the posting process.
214 | - Usage
215 | - Set the corresponding tags
216 | Set the tag to ignore subtrees with =org-static-blog-no-post-tag=, default is =nonpost=.
217 | - Posting
218 | The parts containing this tag will be automatically ignored during the posting process.
219 | - Example
220 | - If you have an org-mode file containing =tree-1=, =tree-2=, =tree-3=. and only want to publish =tree-1= and =tree-3=, then the file would look like this
221 | #+begin_src org-mode
222 | * tree-1
223 | * tree-2 :nonpost:
224 | * tree-3
225 | #+end_src
226 | - Then the file will automatically ignore =tree-2= subtrees with the =nonpost= tag when it is published.
227 |
228 | *** Extended cleaning of the suggested filename
229 |
230 | If you regularly find yourself editing the suggested filename when creating new
231 | posts, e.g. replacing slashes (`/`), then you can modify the value of
232 | `org-static-blog-suggested-filename-cleaning-regexp`. With its default value,
233 | `"\s"`, it only replaces whitespace.
234 |
235 | For example, a post with the title "Using bastibe/org-static-blog for your blog"
236 | would, with the default result in a suggested filename of
237 | `2023-10-02-using-bastibe/org-static-blog-for-your-blog.org`. If the value of
238 | `org-static-blog-suggested-filename-cleaning-regexp` is changed like this
239 |
240 | ```
241 | (setq org-static-blog-suggested-filename-cleaning-regexp (rx (or "/" (in white)))
242 | ```
243 |
244 | the `/` will be replaced too and the suggested filename will be
245 | `2023-10-02-using-bastibe-org-static-blog-for-your-blog.org`.
246 |
247 | * Known Issues
248 |
249 | - Org-static-blog is a pure static site generator. As such, it does
250 | not include comments. However, you can easily include services like
251 | Disqus to do this for you.
252 | - You can have hosting services like GitHub auto-render you blog every
253 | time you commit using continuous integration tools like Travis CI.
254 | An example of how to do this has been gracefully provided
255 | by [[https://gitlab.com/_zngguvnf/org-static-blog-example][zngguvnf]].
256 | - Individual blog entries are only re-rendered if no current HTML file
257 | is available (i.e. the org file is older than the HTML file). If you
258 | want to forcibly re-render an entry, delete the HTML file.
259 |
260 | * Changelog
261 |
262 | - 2018-03-17 (v1.0.4): Massive speed up of org-static-blog. A
263 | re-render with one changed file used to take about a second per
264 | post, and now takes about a second total.
265 | - 2018-03-21 (v1.1.0): Tags.
266 | Each post can now have tags (using =#+tags:=). If you enable
267 | =org-static-blog-enable-tags=, tags are included in each post,
268 | tag-index pages are generated for each tag, and a tag archive
269 | is generated for all tags.
270 | - 2018-03-23 (v1.1.1): Tags.
271 | Deprecated =#+tags:= in favor of =#+filetags:=, which is the
272 | correct way of setting file-wide tags in org-mode.
273 | (Thank you, Kaushal Modi!)
274 | - 2018-04-19 (v1.2.0): HTML5
275 | Org-static-blog now outputs valid HTML5 instead of XHTML. This makes
276 | the resulting HTML cleaner, but shouldn't impact your styles. Also,
277 | you can now customize your content language by setting
278 | =org-static-blog-langcode= and the HTML output has been fixed in a few
279 | places.
280 | (Thank you, Michael Cardell Widerkrantz!)
281 | - 2020-03-20 (v1.3.0): Nested directories, Translations, and more
282 | Improve handling of local variables (Thank you, Matthew Bauer)
283 | Rewrote README in org-mode (Thank you, Rafał -rsm- Marek)
284 | Adds support for localizations (Thank you, Rafał -rsm- Marek)
285 | Put license in a LICENSE file (Thank you, Jonas Bernoulli)
286 | Adds uption to force-rerender entire blog (Thank you, Winny)
287 | Support for non-flat directory structure (Thank you, Shmavon Gazanchyan)
288 | Support for "preview" slugs on index page (Thank you, K. Scarlet)
289 | Various bugfixes (Thank you, Matthew Bauer, luhuaei, neeasade, Yauhen Makei, Winny, zsxh)
290 | Translations in RU, BY, FR (Thank you, Yauhen Makei, Théo Jacquin)
291 | - 2020-07-20 (v1.4.0):
292 | Adds a command to create drafts (Thank you, Massimo Lauria)
293 | Adds optional RSS info (Thank you, Massimo Lauria)
294 | Restructures preamble and postamble to be more consistent (Thank you, Massimo Lauria)
295 | Translations in IT, ES (Thank you, Massimo Lauria, Alberto Álvarez)
296 | Option to make ellipsis link to full post (Thank you, jaor)
297 | Improves preview generation (Thank you, Allo)
298 | Render RSS dates as per RFT-822 and the RSS spec
299 | - 2021-03-05 (v1.5.0)
300 | Better awareness for posts in subdirectories (Thank you, Justin Abrahms)
301 | New custom variable org-static-blog-rss-max-entries (Thank you, jao)
302 | Can now exclude some posts from RSS feeds (Thank you, jao)
303 | New custom variable for index page header (Thank you, Bruno Deremble)
304 | - 2022-05-05 (v1.6.0)
305 | Adds ~#+description~ support that fills the description meta tag (Thank you, Guangwang Huang)
306 | Adds optional post slugs, and date before title (Thank you, jao)
307 | Correct date encoding in RSS and various RSS fixes (Thank you, Agnessa Bubowska)
308 | Ability to not publish subtrees by tag (Thank you, wangz)
309 | Fixes some warnings related to Emacs 28 (Thank you, Maciej Barć)
310 | - 2025-03-20 (v1.7.0)
311 | Fixes path related issues for drafts
312 | Optionally disables comments if a customizable tag is set
313 | Adds viewport metadata to header (Thank you, Aze)
314 | Various fixes to blog generation (Thank you, Aze)
315 | Makes whitespace cleaning of filename suggestions customizable (Thank you, Magnus Therning)
316 | Adds support for Open Graph meta tags (Thank you, Pablo González Carrizo)
317 | Various bugfixes (Thank you, Dou Meishi, Musa Al-hassy, Alexis Purslane, chenyo, Johnny5, Miao ZhiCheng)
318 |
319 | * LICENSE
320 |
321 | Copyright 2015, Bastian Bechtold
322 |
323 | Redistribution and use in source and binary forms, with or without
324 | modification, are permitted provided that the following conditions are
325 | met:
326 |
327 | 1. Redistributions of source code must retain the above copyright
328 | notice, this list of conditions and the following disclaimer.
329 |
330 | 2. Redistributions in binary form must reproduce the above copyright
331 | notice, this list of conditions and the following disclaimer in the
332 | documentation and/or other materials provided with the
333 | distribution.
334 |
335 | 3. Neither the name of the copyright holder nor the names of its
336 | contributors may be used to endorse or promote products derived
337 | from this software without specific prior written permission.
338 |
339 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
340 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
341 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
342 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
343 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
344 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
345 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
346 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
347 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
348 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
349 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
350 |
351 |
--------------------------------------------------------------------------------
/org-static-blog.el:
--------------------------------------------------------------------------------
1 | ;;; org-static-blog.el --- a simple org-mode based static blog generator
2 |
3 | ;; Author: Bastian Bechtold
4 | ;; Contrib: Shmavon Gazanchyan, Rafał -rsm- Marek, neeasade,
5 | ;; Michael Cardell Widerkrantz, Matthew Bauer, Winny, Yauhen Makei,
6 | ;; luhuaei, zngguvnf, Qiantan Hong, Jonas Bernoulli, Théo Jacquin,
7 | ;; K. Scarlet, zsxh
8 | ;; URL: https://github.com/bastibe/org-static-blog
9 | ;; Version: 1.7.0
10 | ;; Package-Requires: ((emacs "24.3"))
11 |
12 | ;;; Commentary:
13 |
14 | ;; Static blog generators are a dime a dozen. This is one more, which
15 | ;; focuses on being simple. All files are simple org-mode files in a
16 | ;; directory. The only requirement is that every org file must have a
17 | ;; #+TITLE and a #+DATE, and optionally, #+FILETAGS.
18 |
19 | ;; This file is also available from marmalade and melpa-stable.
20 |
21 | ;; Set up your blog by customizing org-static-blog's parameters, then
22 | ;; call M-x org-static-blog-publish to render the whole blog or
23 | ;; M-x org-static-blog-publish-file filename.org to render only only
24 | ;; the file filename.org.
25 |
26 | ;; Above all, I tried to make org-static-blog as simple as possible.
27 | ;; There are no magic tricks, and all of the source code is meant to
28 | ;; be easy to read, understand and modify.
29 |
30 | ;; If you have questions, if you find bugs, or if you would like to
31 | ;; contribute something to org-static-blog, please open an issue or
32 | ;; pull request on Github.
33 |
34 | ;; Finally, I would like to remind you that I am developing this
35 | ;; project for free, and in my spare time. While I try to be as
36 | ;; accomodating as possible, I can not guarantee a timely response to
37 | ;; issues. Publishing Open Source Software on Github does not imply an
38 | ;; obligation to *fix your problem right now*. Please be civil.
39 |
40 | ;;; Code:
41 |
42 | (require 'cl-extra)
43 | (require 'org)
44 | (require 'ox-html)
45 |
46 | (defgroup org-static-blog nil
47 | "Settings for a static blog generator using org-mode"
48 | :version "1.6.0"
49 | :group 'applications)
50 |
51 | (defcustom org-static-blog-publish-url "https://example.com/"
52 | "URL of the blog."
53 | :type '(string)
54 | :safe t)
55 |
56 | (defcustom org-static-blog-publish-title "Example.com"
57 | "Title of the blog."
58 | :type '(string)
59 | :safe t)
60 |
61 | (defcustom org-static-blog-publish-directory "~/blog/"
62 | "Directory where published HTML files are stored."
63 | :type '(directory))
64 |
65 | (defcustom org-static-blog-posts-directory "~/blog/posts/"
66 | "Directory where published ORG files are stored.
67 | When publishing, posts are rendered as HTML, and included in the
68 | index, archive, tags, and RSS feed."
69 | :type '(directory))
70 |
71 | (defcustom org-static-blog-drafts-directory "~/blog/drafts/"
72 | "Directory where unpublished ORG files are stored.
73 | When publishing, draft are rendered as HTML, but not included in
74 | the index, archive, tags, or RSS feed."
75 | :type '(directory))
76 |
77 | (defcustom org-static-blog-index-file "index.html"
78 | "File name of the blog landing page.
79 | The index page contains the most recent
80 | `org-static-blog-index-length` full-text posts."
81 | :type '(string)
82 | :safe t)
83 |
84 | (defcustom org-static-blog-index-length 5
85 | "Number of articles to include on index page."
86 | :type '(integer)
87 | :safe t)
88 |
89 | (defcustom org-static-blog-archive-file "archive.html"
90 | "File name of the list of all blog posts.
91 | The archive page lists all posts as headlines."
92 | :type '(string)
93 | :safe t)
94 |
95 | (defcustom org-static-blog-tags-file "tags.html"
96 | "File name of the list of all blog posts by tag.
97 | The tags page lists all posts as headlines."
98 | :type '(string)
99 | :safe t)
100 |
101 | (defcustom org-static-blog-enable-tags nil
102 | "Show tags below posts, and generate tag pages."
103 | :group 'org-static-blog
104 | :type '(boolean)
105 | :safe t)
106 |
107 | (defcustom org-static-blog-enable-deprecation-warning t
108 | "Show deprecation warnings."
109 | :type '(boolean))
110 |
111 | (defcustom org-static-blog-rss-file "rss.xml"
112 | "File name of the RSS feed."
113 | :type '(string)
114 | :safe t)
115 |
116 | (defcustom org-static-blog-rss-excluded-tag nil
117 | "Posts with this tag won't be included in the RSS feeds."
118 | :type '(choice (const :tag "None" nil)
119 | (string :tag "Tag name"))
120 | :safe t)
121 |
122 | (defcustom org-static-blog-no-comments-tag nil
123 | "Posts with this tag won't include comments."
124 | :type '(choice (const :tag "None" nil)
125 | (string :tag "Tag name"))
126 | :safe t)
127 |
128 | (defcustom org-static-blog-rss-extra ""
129 | "Extra information for the RSS feed header.
130 | This information is placed right before the sequence of posts.
131 | You can add an icon for the feed, or advertise that you built
132 | your blog with emacs, org-mode and org-static-blog.
133 | "
134 | :type '(string)
135 | :safe t)
136 |
137 | (defcustom org-static-blog-rss-max-entries nil
138 | "Maximum number of entries in the RSS feed.
139 | If nil (the default), all existing posts are included."
140 | :type '(choice (const nil) integer)
141 | :safe t)
142 |
143 | (defcustom org-static-blog-enable-tag-rss nil
144 | "Whether to generate per tag RSS feeds.
145 |
146 | When this flag is set, an RSS file with name given by prefixing
147 | `org-static-blog-rss-file' with '-' is created for each
148 | existing tag. The options `org-static-blog-rss-extra',
149 | `org-static-blog-rss-max-entries' and
150 | `org-static-blog-rss-excluded-tag' are also used to construct
151 | per-tag RSS feeds."
152 | :type '(boolean))
153 |
154 | (defcustom org-static-blog-page-header ""
155 | "HTML to put in the of each page."
156 | :type '(string)
157 | :safe t)
158 |
159 | (defcustom org-static-blog-page-preamble ""
160 | "HTML to put before the content of each page."
161 | :type '(string)
162 | :safe t)
163 |
164 | (defcustom org-static-blog-page-postamble ""
165 | "HTML to put after the content of each page."
166 | :type '(string)
167 | :safe t)
168 |
169 | (defcustom org-static-blog-index-front-matter ""
170 | "HTML to put at the beginning of the index page."
171 | :type '(string)
172 | :safe t)
173 |
174 | (defcustom org-static-blog-post-preamble-text ""
175 | "HTML to put before every post"
176 | :type '(string)
177 | :safe t)
178 |
179 | (defcustom org-static-blog-post-postamble-text ""
180 | "HTML to put before every post"
181 | :type '(string)
182 | :safe t)
183 |
184 | (defcustom org-static-blog-post-comments ""
185 | "HTML code for comments to put after each blog post."
186 | :type '(string)
187 | :safe t)
188 |
189 | (defcustom org-static-blog-langcode "en"
190 | "Language code for the blog content."
191 | :type '(string)
192 | :safe t)
193 |
194 | (defcustom org-static-blog-use-preview nil
195 | "Use preview versions of posts on multipost pages.
196 |
197 | See also `org-static-blog-preview-start',
198 | `org-static-blog-preview-end', `org-static-blog-preview-ellipsis'
199 | and `org-static-blog-preview-link-p'."
200 | :type '(boolean)
201 | :safe t)
202 |
203 | (defcustom org-static-blog-preview-start nil
204 | "Marker indicating the beginning of a post's preview.
205 |
206 | When set to nil, we look for the first occurence of
in the
207 | generated HTML. See also `org-static-blog-preview-end'."
208 | :type '(choice (const :tag "First paragraph" nil) (string))
209 | :safe t)
210 |
211 | (defcustom org-static-blog-preview-end nil
212 | "Marker indicating the end of a post's preview.
213 |
214 | When set to nil, we look for the first occurence of
after
215 | `org-static-blog-preview-start' (or the first
if that is nil)
216 | in the generated HTML."
217 | :type '(choice (const :tag "First paragraph" nil) (string))
218 | :safe t)
219 |
220 | (defcustom org-static-blog-preview-convert-titles t
221 | "When preview is enabled, convert
to
for the previews."
222 | :type '(boolean)
223 | :safe t)
224 |
225 | (defcustom org-static-blog-preview-ellipsis "(...)"
226 | "The HTML appended to the preview if some part of the post is hidden.
227 |
228 | The contents shown in the preview is determined by the values of
229 | the variables `org-static-blog-preview-start' and
230 | `org-static-blog-preview-end'."
231 | :type '(string)
232 | :safe t)
233 |
234 | (defcustom org-static-blog-no-post-tag "nonpost"
235 | "Do not pushlish the subtree with this tag or property."
236 | :type '(string)
237 | :safe t)
238 |
239 | (defcustom org-static-blog-preview-link-p nil
240 | "Whether to make the preview ellipsis a link to the article's page."
241 | :type '(boolean)
242 | :safe t)
243 |
244 | (defcustom org-static-blog-preview-date-first-p nil
245 | "If t, print post dates before title in the preview view."
246 | :type '(boolean)
247 | :safe t)
248 |
249 | (defcustom org-static-blog-suggested-filename-cleaning-regexp "\s"
250 | "Regexp used to clean the suggested filename."
251 | :type'(string)
252 | :safe t)
253 |
254 | (defcustom org-static-blog-enable-og-tags nil
255 | "Whether to generate Open Graph Protocol meta tags"
256 | :group 'org-static-blog
257 | :type '(boolean)
258 | :safe t)
259 |
260 | (defcustom org-static-blog-image ""
261 | "Default image relative url to be used as Open Graph image for posts.
262 |
263 | Only if og tags are enabled. It can be overridden with the
264 | `#+image` property in specfic posts."
265 | :type '(string)
266 | :safe t)
267 |
268 | ;; localization support
269 | (defconst org-static-blog-texts
270 | '((other-posts
271 | ("en" . "Other posts")
272 | ("pl" . "Pozostałe wpisy")
273 | ("ru" . "Другие публикации")
274 | ("by" . "Іншыя публікацыі")
275 | ("it" . "Altri articoli")
276 | ("es" . "Otros artículos")
277 | ("fr" . "Autres articles")
278 | ("zh" . "其他帖子")
279 | ("ja" . "他の投稿"))
280 | (date-format
281 | ("en" . "%d %b %Y")
282 | ("pl" . "%Y-%m-%d")
283 | ("ru" . "%d.%m.%Y")
284 | ("by" . "%d.%m.%Y")
285 | ("it" . "%d/%m/%Y")
286 | ("es" . "%d/%m/%Y")
287 | ("fr" . "%d-%m-%Y")
288 | ("zh" . "%Y-%m-%d")
289 | ("ja" . "%Y/%m/%d"))
290 | (tags
291 | ("en" . "Tags")
292 | ("pl" . "Tagi")
293 | ("ru" . "Ярлыки")
294 | ("by" . "Ярлыкі")
295 | ("it" . "Categorie")
296 | ("es" . "Categoría")
297 | ("fr" . "Tags")
298 | ("zh" . "标签")
299 | ("ja" . "タグ"))
300 | (archive
301 | ("en" . "Archive")
302 | ("pl" . "Archiwum")
303 | ("ru" . "Архив")
304 | ("by" . "Архіў")
305 | ("it" . "Archivio")
306 | ("es" . "Archivo")
307 | ("fr" . "Archive")
308 | ("zh" . "归档")
309 | ("ja" . "アーカイブ"))
310 | (posts-tagged
311 | ("en" . "Posts tagged")
312 | ("pl" . "Wpisy z tagiem")
313 | ("ru" . "Публикации с ярлыками")
314 | ("by" . "Публікацыі")
315 | ("it" . "Articoli nella categoria")
316 | ("es" . "Artículos de la categoría")
317 | ("fr" . "Articles tagués")
318 | ("zh" . "打标签的帖子")
319 | ("ja" . "タグ付けされた投稿"))
320 | (no-prev-post
321 | ("en" . "There is no previous post")
322 | ("pl" . "Poprzedni wpis nie istnieje")
323 | ("ru" . "Нет предыдущей публикации")
324 | ("by" . "Няма папярэдняй публікацыі")
325 | ("it" . "Non c'è nessun articolo precedente")
326 | ("es" . "No existe un artículo precedente")
327 | ("fr" . "Il n'y a pas d'article précédent")
328 | ("zh" . "无更旧的帖子")
329 | ("ja" . "前の投稿はありません"))
330 | (no-next-post
331 | ("en" . "There is no next post")
332 | ("pl" . "Następny wpis nie istnieje")
333 | ("ru" . "Нет следующей публикации")
334 | ("by" . "Няма наступнай публікацыі")
335 | ("it" . "Non c'è nessun articolo successivo")
336 | ("es" . "No hay artículo siguiente")
337 | ("fr" . "Il n'y a pas d'article suivants")
338 | ("zh" . "无更新的帖子")
339 | ("ja" . "次の投稿はありません"))
340 | (title
341 | ("en" . "Title: ")
342 | ("pl" . "Tytuł: ")
343 | ("ru" . "Заголовок: ")
344 | ("by" . "Загаловак: ")
345 | ("it" . "Titolo: ")
346 | ("es" . "Título: ")
347 | ("fr" . "Titre : ")
348 | ("zh" . "标题:")
349 | ("ja" . "タイトル: "))
350 | (filename
351 | ("en" . "Filename: ")
352 | ("pl" . "Nazwa pliku: ")
353 | ("ru" . "Имя файла: ")
354 | ("by" . "Імя файла: ")
355 | ("it" . "Nome del file: ")
356 | ("es" . "Nombre del archivo: ")
357 | ("fr" . "Nom du fichier :")
358 | ("zh" . "文件名:")
359 | ("ja" . "ファイル名: "))))
360 |
361 | (defun concat-to-dir (dir filename)
362 | "Concat filename to another path interpreted as a directory."
363 | (concat (file-name-as-directory dir) filename))
364 |
365 | (defun org-static-blog-template (tTitle tContent &optional tDescription tImage tUrl)
366 | "Create the template that is used to generate the static pages."
367 | (concat
368 | "\n"
369 | "\n"
370 | "\n"
371 | "\n"
372 | (when tDescription
373 | (format "\n" tDescription))
374 | "\n"
378 | "" tTitle "\n"
379 |
380 | (when org-static-blog-enable-og-tags
381 | (concat
382 | "\n"
383 | "\n"
384 | (when tDescription
385 | (format "\n" tDescription))
386 | (when tUrl
387 | (format "\n" tUrl))
388 | (if tImage
389 | (format "\n"
390 | (org-static-blog-get-absolute-url tImage))
391 | (when (> (length org-static-blog-image) 0)
392 | (format "\n"
393 | (org-static-blog-get-absolute-url org-static-blog-image))))))
394 | org-static-blog-page-header
395 | "\n"
396 | "\n"
397 | "
"
398 | org-static-blog-page-preamble
399 | "
\n"
400 | "
\n"
401 | tContent
402 | "
\n"
403 | "
"
404 | org-static-blog-page-postamble
405 | "
\n"
406 | "\n"
407 | "\n"))
408 |
409 | (defun org-static-blog-gettext (text-id)
410 | "Return localized text.
411 | Depends on org-static-blog-langcode and org-static-blog-texts."
412 | (let* ((text-node (assoc text-id org-static-blog-texts))
413 | (text-lang-node (if text-node
414 | (assoc org-static-blog-langcode text-node)
415 | nil)))
416 | (if text-lang-node
417 | (cdr text-lang-node)
418 | (concat "[" (symbol-name text-id) ":" org-static-blog-langcode "]"))))
419 |
420 |
421 | ;;;###autoload
422 | (defun org-static-blog-publish (&optional force-render)
423 | "Render all blog posts, the index, archive, tags, and RSS feed.
424 | Only blog posts that changed since the HTML was created are
425 | re-rendered.
426 |
427 | With a prefix argument, all blog posts are re-rendered
428 | unconditionally."
429 | (interactive "P")
430 | (dolist (file (append (org-static-blog-get-post-filenames)
431 | (org-static-blog-get-draft-filenames)))
432 | (when (or force-render (org-static-blog-needs-publishing-p file))
433 | (org-static-blog-publish-file file)))
434 | ;; don't spam too many deprecation warnings:
435 | (let ((org-static-blog-enable-deprecation-warning nil))
436 | (org-static-blog-assemble-index)
437 | (org-static-blog-assemble-rss)
438 | (org-static-blog-assemble-archive)
439 | (if org-static-blog-enable-tags
440 | (org-static-blog-assemble-tags))))
441 |
442 | (defun org-static-blog-needs-publishing-p (post-filename)
443 | "Check whether POST-FILENAME was changed since last render."
444 | (let ((pub-filename
445 | (org-static-blog-matching-publish-filename post-filename)))
446 | (not (and (file-exists-p pub-filename)
447 | (file-newer-than-file-p pub-filename post-filename)))))
448 |
449 | (defun org-static-blog-matching-publish-filename (post-filename)
450 | "Generate HTML file name for POST-FILENAME."
451 | (concat-to-dir org-static-blog-publish-directory
452 | (org-static-blog-get-post-public-path post-filename)))
453 |
454 | (defun org-static-blog-get-post-filenames ()
455 | "Returns a list of all posts."
456 | (directory-files-recursively
457 | org-static-blog-posts-directory ".*\\.org$"))
458 |
459 | (defun org-static-blog-get-draft-filenames ()
460 | "Returns a list of all drafts."
461 | (directory-files-recursively
462 | org-static-blog-drafts-directory ".*\\.org$"))
463 |
464 | (defun org-static-blog-file-buffer (file)
465 | "Return the buffer open with a full filepath, or nil."
466 | (require 'seq)
467 | (make-directory (file-name-directory file) t)
468 | (car (seq-filter
469 | (lambda (buf)
470 | (string= (with-current-buffer buf buffer-file-name) file))
471 | (buffer-list))))
472 |
473 | ;; This macro is needed for many of the following functions.
474 | (defmacro org-static-blog-with-find-file (file contents &rest body)
475 | "Executes BODY in FILE. Use this to insert text into FILE.
476 | The buffer is disposed after the macro exits (unless it already
477 | existed before)."
478 | `(save-excursion
479 | (let ((current-buffer (current-buffer))
480 | (buffer-exists (org-static-blog-file-buffer ,file))
481 | (result nil)
482 | (auto-insert nil)
483 | (contents ,contents))
484 | (if buffer-exists
485 | (switch-to-buffer buffer-exists)
486 | (find-file ,file))
487 | (erase-buffer)
488 | (insert contents)
489 | (setq result (progn ,@body))
490 | (basic-save-buffer)
491 | (unless buffer-exists
492 | (kill-buffer))
493 | (switch-to-buffer current-buffer)
494 | result)))
495 |
496 | (defun org-static-blog-get-date (post-filename)
497 | "Extract the `#+date:` from POST-FILENAME as date-time."
498 | (let ((case-fold-search t))
499 | (with-temp-buffer
500 | (insert-file-contents post-filename)
501 | (goto-char (point-min))
502 | (if (search-forward-regexp "^\\#\\+date:[ ]*[[<]?\\([^]>]+\\)[]>]?$" nil t)
503 | (date-to-time (match-string 1))
504 | (time-since 0)))))
505 |
506 | (defun org-static-blog-get-title (post-filename)
507 | "Extract the `#+title:` from POST-FILENAME."
508 | (let ((case-fold-search t))
509 | (with-temp-buffer
510 | (insert-file-contents post-filename)
511 | (goto-char (point-min))
512 | (if (search-forward-regexp "^\\#\\+title:[ ]*\\(.+\\)$" nil t)
513 | (match-string 1)
514 | (warn "%s file does not have a title, using %s as the title" post-filename post-filename)
515 | post-filename))))
516 |
517 | (defun org-static-blog-get-description (post-filename)
518 | "Extract the `#+description:` from POST-FILENAME."
519 | (let ((case-fold-search t))
520 | (with-temp-buffer
521 | (insert-file-contents post-filename)
522 | (goto-char (point-min))
523 | (when (search-forward-regexp "^\\#\\+description:[ ]*\\(.+\\)$" nil t)
524 | (let ((description (string-trim (match-string 1))))
525 | (unless (zerop (length description))
526 | description))))))
527 |
528 | (defun org-static-blog-get-image (post-filename)
529 | "Extract the `#+image:` from POST-FILENAME."
530 | (let ((case-fold-search t))
531 | (with-temp-buffer
532 | (insert-file-contents post-filename)
533 | (goto-char (point-min))
534 | (when (search-forward-regexp "^\\#\\+image:[ ]*\\(.+\\)$" nil t)
535 | (let ((image (string-trim (match-string 1))))
536 | (unless (zerop (length image))
537 | image))))))
538 |
539 | (defun org-static-blog-get-tags (post-filename)
540 | "Extract the `#+filetags:` from POST-FILENAME as list of strings."
541 | (let ((case-fold-search t))
542 | (with-temp-buffer
543 | (insert-file-contents post-filename)
544 | (goto-char (point-min))
545 | (if (search-forward-regexp "^\\#\\+filetags:[ ]*:\\(.*\\):$" nil t)
546 | (split-string (match-string 1) ":")
547 | (if (search-forward-regexp "^\\#\\+filetags:[ ]*\\(.+\\)$" nil t)
548 | (split-string (match-string 1))
549 | )))))
550 |
551 | (defun org-static-blog-get-tag-tree ()
552 | "Return an association list of tags to filenames.
553 | e.g. `(('foo' 'file1.org' 'file2.org') ('bar' 'file2.org'))`"
554 | (let ((tag-tree '()))
555 | (dolist (post-filename (org-static-blog-get-post-filenames))
556 | (let ((tags (org-static-blog-get-tags post-filename)))
557 | (dolist (tag (remove org-static-blog-rss-excluded-tag tags))
558 | (if (assoc-string tag tag-tree t)
559 | (push post-filename (cdr (assoc-string tag tag-tree t)))
560 | (push (cons tag (list post-filename)) tag-tree)))))
561 | tag-tree))
562 |
563 | (defun org-static-blog--preview-region ()
564 | "Find the start and end of the preview in the current buffer."
565 | (goto-char (point-min))
566 | (if org-static-blog-preview-end
567 | (when (or (search-forward (or org-static-blog-preview-start "
\n"))))
757 |
758 |
759 |
760 | (defun org-static-blog-post-preamble (post-filename)
761 | "Returns the formatted date and headline of the post.
762 | This function is called for every post and prepended to the post body.
763 | Modify this function if you want to change a posts headline."
764 | (concat
765 | org-static-blog-post-preamble-text
766 | "
\n"))
772 |
773 |
774 | (defun org-static-blog-post-taglist (post-filename)
775 | "Returns the tag list of the post.
776 | This part will be attached at the end of the post, after
777 | the taglist, in a
...
block."
778 | (let ((taglist-content "")
779 | (tags (remove org-static-blog-no-comments-tag
780 | (remove org-static-blog-rss-excluded-tag
781 | (org-static-blog-get-tags post-filename)))))
782 | (when (and tags org-static-blog-enable-tags)
783 | (setq taglist-content (concat "" (org-static-blog-gettext 'tags) ": "))
786 | (dolist (tag tags)
787 | (setq taglist-content (concat taglist-content "" tag " "))))
790 | taglist-content))
791 |
792 | (defun org-static-blog-post-postamble (post-filename)
793 | "Returns the tag list and comment box at the end of a post.
794 | This function is called for every post and the returned string is
795 | appended to the post body, and includes the tag list generated by
796 | followed by the HTML code for comments."
797 | (concat "
"))
806 | org-static-blog-post-postamble-text))
807 |
808 |
809 | (defun org-static-blog--prune-items (items)
810 | "Limit, if needed, the items to be included in an RSS feed."
811 | (if (and org-static-blog-rss-max-entries (> org-static-blog-rss-max-entries 0))
812 | (let ((excess (- (length items) org-static-blog-rss-max-entries)))
813 | (if (> excess 0) (butlast items excess) items))
814 | items))
815 |
816 | (defun org-static-blog--rss-filename (&optional tag)
817 | "Full path to the RSS file for the given TAG."
818 | (concat-to-dir org-static-blog-publish-directory
819 | (concat tag (when tag "-") org-static-blog-rss-file)))
820 |
821 | (defun org-static-blog--write-rss (items &optional tag)
822 | "Generates an RSS file for the given TAG, or for all tags is TAG is nil."
823 | (let ((title (format "%s%s"
824 | org-static-blog-publish-title
825 | (if tag (concat " - " tag) "")))
826 | (url (format "%s%s"
827 | org-static-blog-publish-url
828 | (if tag (concat "/tag-" (downcase tag) ".html") "")))
829 | (items (sort items (lambda (x y) (time-less-p (car y) (car x))))))
830 | (org-static-blog-with-find-file
831 | (org-static-blog--rss-filename tag)
832 | (concat "\n"
833 | "\n"
834 | "\n"
835 | "\n"
836 | "\n"
837 | "" url "\n"
838 | "" (let ((system-time-locale "C")) ; force dates to render as per RSS spec
839 | (format-time-string "%a, %d %b %Y %H:%M:%S %z" (current-time)))
840 | "\n"
841 | org-static-blog-rss-extra
842 | (apply 'concat (mapcar 'cdr (org-static-blog--prune-items items)))
843 | "\n"
844 | "\n"))))
845 |
846 | (defun org-static-blog-assemble-rss ()
847 | "Assemble the blog RSS feed.
848 | The RSS-feed is an XML file that contains every blog post in a
849 | machine-readable format."
850 | (let ((system-time-locale "en_US.utf-8") ; force dates to render as per RSS spec
851 | (rss-items nil)
852 | (rss-tag-items nil))
853 | (dolist (post-filename (org-static-blog-get-post-filenames))
854 | (let ((rss-date (org-static-blog-get-date post-filename))
855 | (rss-text (org-static-blog-get-rss-item post-filename))
856 | (tags (org-static-blog-get-tags post-filename)))
857 | (when (or (null org-static-blog-rss-excluded-tag)
858 | (not (member org-static-blog-rss-excluded-tag tags)))
859 | (let ((item (cons rss-date rss-text)))
860 | (add-to-list 'rss-items item)
861 | (when org-static-blog-enable-tag-rss
862 | (dolist (tag tags)
863 | (let ((items (cons item (cdr (assoc tag rss-tag-items)))))
864 | (setf (alist-get tag rss-tag-items nil t 'string=) items))))))))
865 | (org-static-blog--write-rss rss-items)
866 | (message "%s" rss-tag-items)
867 | (dolist (x rss-tag-items) (org-static-blog--write-rss (cdr x) (car x)))))
868 |
869 | (defun org-static-blog-get-rss-item (post-filename)
870 | "Assemble RSS item from post-filename.
871 | The HTML content is taken from the rendered HTML post."
872 | (concat
873 | "\n"
874 | " \n"
875 | " \n"
878 | (let ((categories ""))
879 | (when (and (org-static-blog-get-tags post-filename) org-static-blog-enable-tags)
880 | (dolist (tag (org-static-blog-get-tags post-filename))
881 | (setq categories (concat categories
882 | " \n"))))
883 | categories)
884 | " "
885 | (url-encode-url (org-static-blog-get-post-url post-filename))
886 | "\n"
887 | " "
888 | (url-encode-url (org-static-blog-get-post-url post-filename))
889 | "\n"
890 | " "
891 | (let ((system-time-locale "C")) ; force dates to render as per RSS spec
892 | (format-time-string "%a, %d %b %Y %H:%M:%S %z" (org-static-blog-get-date post-filename)))
893 | "\n"
894 | "\n"))
895 |
896 | (defun org-static-blog-assemble-archive ()
897 | "Re-render the blog archive page.
898 | The archive page contains single-line links and dates for every
899 | blog post, but no post body."
900 | (let ((archive-filename (concat-to-dir org-static-blog-publish-directory org-static-blog-archive-file))
901 | (archive-entries nil)
902 | (post-filenames (org-static-blog-get-post-filenames)))
903 | (setq post-filenames (sort post-filenames (lambda (x y) (time-less-p
904 | (org-static-blog-get-date y)
905 | (org-static-blog-get-date x)))))
906 | (org-static-blog-with-find-file
907 | archive-filename
908 | (org-static-blog-template
909 | org-static-blog-publish-title
910 | (concat
911 | "
" (org-static-blog-gettext 'archive) "
\n"
912 | (apply 'concat (mapcar 'org-static-blog-get-post-summary post-filenames)))))))
913 |
914 | (defun org-static-blog-get-post-summary (post-filename)
915 | "Assemble post summary for an archive page.
916 | This function is called for every post on the archive and
917 | tags-archive page. Modify this function if you want to change an
918 | archive headline."
919 | (concat
920 | "