This commit is contained in:
Yann Esposito (Yogsototh) 2019-08-17 19:22:22 +02:00
parent 0a5d0ed2ff
commit a902bfbd38
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
7 changed files with 126 additions and 371 deletions

Binary file not shown.

6
deploy.sh Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env zsh
rootdir=${0:h}
echo $rootdir
rsync --progress --partial -avHe ssh $rootdir/_site/ root@shoggoth1:/var/www/her.esy.fun/ --delete

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

View file

@ -1,338 +0,0 @@
#+TITLE: Blogging with org-mode
#+SUBTITLE: Meta post about this blog
#+AUTHOR: Yann Esposito
#+EMAIL: yann@esposito.host
#+DATE: [2019-07-27]
#+KEYWORDS: programming, blog, org-mode, meta
#+OPTIONS: auto-id:t toc:t
* Introduction
:PROPERTIES:
:CUSTOM_ID: introduction
:END:
#+begin_quote
/tl;dr/: [[#full-solution][Go to the full solution]] for the impatients.
#+end_quote
As the first article of my new blogging system, let's have a meta blog post.
Once in a while, I like to change how I blog.
A long time ago, I used PHP for my first website using my personal space at my
university.
And as of today, a clond of my university website still exists:
http://yann.esposito.free.fr It contains all details about my old research
activities and teaching resources for my students.
Then I used [[http://nanoc.ws][nanoc]], a ruby static website generator.
Then I switched to [[https://jaspervdj.be/hakyll/][hakyll]] because I wanted to switch to a Haskell's written
tool.
This time I'll try to use [[http://orgmode.org][org-mode]] directly with [[https://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][org-publish]].
I used vim a lot, but recently I switched to emacs.
The main reason were:
1. You use LISP as plugin language while for Vim it is vimscript which is a
quite terrible language.
2. Many comment on the Internet about people telling that [[https://github.com/emacs-evil/evil][evil]] (vim keybdings in
emacs) was really really good. And I can confirm it is.
3. The [[http://spacemacs.org][spacemacs]] project made it easier to handle the emacs configuratin
complexity for a newbie.
And by doing the switch, I discovered a lot of great emacs packages.
Now I really do not regret the switch.
In particular, [[http://orgmode.org][org-mode]] has been a game changer.
I used to write markdown before.
But now I use the org format.
It is superior for my usage(s).
* How?
:PROPERTIES:
:CUSTOM_ID: how
:END:
You can easily follow the org-mode doc to make your own website.
But I have added a few niceties.
First, the code to export is provided in a local file.
I've got a system to auto-load elisp file when entering in a new project.
It is quite safe as it uses PGP and check if I trust the person who encrypted
the file.
See [[file:project-el/index.org][my other post about it]].
** Configuration variables
:PROPERTIES:
:CUSTOM_ID: configuration-variables
:END:
#+begin_src elisp
;; CONFIGURATION VARIABLES
(setq domainname "https://her.esy.fun")
(setq base-dir (concat (projectile-project-root) "src"))
(setq publish-dir (concat (projectile-project-root) "_site"))
(setq assets-dir (concat base-dir "/"))
(setq publish-assets-dir (concat publish-dir "/"))
(setq rss-dir base-dir)
(setq rss-title "Subscribe to articles")
(setq publish-rss-dir publish-dir)
(setq css-path "/css/minimalist.css")
(setq author-name "Yann Esposito")
(setq author-email "yann@esposito.host")
#+end_src
* Full Solution
:PROPERTIES:
:CUSTOM_ID: full-solution
:END:
#+begin_src elisp
(setq domainname "https://her.esy.fun")
(setq base-dir (concat (projectile-project-root) "src"))
(setq publish-dir (concat (projectile-project-root) "_site"))
(setq assets-dir (concat base-dir "/"))
(setq publish-assets-dir (concat publish-dir "/"))
(setq rss-dir base-dir)
(setq rss-title "Subscribe to articles")
(setq publish-rss-dir publish-dir)
(setq css-path "/css/minimalist.css")
(setq author-name "Yann Esposito")
(setq author-email "yann@esposito.host")
(require 'org)
(require 'ox-publish)
(require 'ox-html)
(require 'org-element)
(require 'ox-rss)
(setq org-link-file-path-type 'relative)
(setq org-publish-timestamp-directory
(concat (projectile-project-root) "_cache/"))
(defvar org-blog-head
(concat
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" css-path "\"/>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<link rel=\"alternative\" type=\"application/rss+xml\" title=\"" rss-title "\" href=\"/archives.xml\" />"
"<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\">"))
(defun menu (lst)
"Blog menu"
(concat
"<navigation>"
(mapconcat 'identity
(append
'("<a href=\"/index.html\">Home</a>"
"<a href=\"/archive.html\">Posts</a>"
"<a href=\"/about-me.html\">About</a>")
lst)
" | ")
"</navigation>"))
(defun str-time-to-year-float (date-str)
(/ (float-time
(apply 'encode-time
(mapcar (lambda (x) (if (null x) 0 x))
(parse-time-string date-str))))
(* 365.25 24 60 60)))
(defvar blog-creation-date "2019-07-01")
(defun y-date (date-str)
"Number of year since the begining of this blog"
(let ((y (- (str-time-to-year-float date-str)
(str-time-to-year-float blog-creation-date))))
(format "∆t=%.2f" y)))
(defun get-from-info (info k)
(let ((i (car (plist-get info k))))
(when (and i (stringp i))
i)))
(defun org-blog-preamble (info)
"Pre-amble for whole blog."
(concat
"<div class=\"content\">"
(menu '("<a href=\"#postamble\">↓ bottom ↓</a>"))
"<h1>"
(format "%s" (car (plist-get info :title)))
"</h1>"
(when-let ((date (plist-get info :date)))
(format "<span class=\"article-date\">%s</span>"
(format-time-string "%Y-%m-%d"
(org-timestamp-to-time
(car date)))))
(when-let ((subtitle (car (plist-get info :subtitle))))
(format "<h2>%s</h2>" subtitle))
"</div>"))
(defun rand-obfs (c)
(let ((r (% (random) 20)))
(cond ;; ((eq 0 r) (format "%c" c))
((<= 0 r 10) (format "&#%d;" c))
(t (format "&#x%X;" c)))))
(defun obfuscate-html (txt)
(apply 'concat
(mapcar 'rand-obfs txt)))
(defun org-blog-postamble (info)
"Post-amble for whole blog."
(concat
"<div class=\"content\">"
;; TODO install a comment system
;; (let ((url (format "%s%s" domainname (replace-regexp-in-string base-dir "" (plist-get info :input-file)))))
;; (format "<a href=\"https://comments.esy.fun/slug/%s\">comment</a>"
;; (url-hexify-string url)))
"<footer>"
(when-let ((author (get-from-info info :author)))
(if-let ((email (plist-get info :email)))
(let* ((obfs-email (obfuscate-html email))
(obfs-author (obfuscate-html author))
(obfs-title (obfuscate-html (get-from-info info :title)))
(full-email (format "%s &lt;%s&gt;" obfs-author obfs-email)))
(format "<div class=\"author\">Author: <a href=\"%s%s%s%s\">%s</a></div>"
(obfuscate-html "mailto:")
full-email
(obfuscate-html "?subject=yblog: ")
obfs-title
full-email))
(format "<div class=\"author\">Author: %s</div>" author)))
(when-let ((date (get-from-info info :date)))
(format "<div class=\"date\">Created: %s (%s)</div>" date (y-date date)))
(when-let ((keywords (plist-get info :keywords)))
(format "<div class=\"keywords\">Keywords: <code>%s</code></div>" keywords))
(format "<div class=\"date\">Generated: %s</div>"
(format-time-string "%Y-%m-%d %H:%M:%S"))
(format (concat "<div class=\"creator\"> Generated with "
"<a href=\"https://www.gnu.org/software/emacs/\" target=\"_blank\" rel=\"noopener noreferrer\">Emacs %s</a>, "
"<a href=\"http://spacemacs.org\" target=\"_blank\" rel=\"noopener noreferrer\">Spacemacs %s</a>, "
"<a href=\"http://orgmode.org\" target=\"_blank\" rel=\"noopener noreferrer\">Org Mode %s</a>"
"</div>")
emacs-version spacemacs-version org-version)
"</footer>"
(menu '("<a href=\"#preamble\">↑ Top ↑</a>"))
"</div>"))
(defun org-blog-sitemap-format-entry (entry _style project)
"Return string for each ENTRY in PROJECT."
(when (s-starts-with-p "posts/" entry)
(format (concat "@@html:<span class=\"archive-item\">"
"<span class=\"archive-date\">@@ %s: @@html:</span>@@"
" [[file:%s][%s]]"
" @@html:</span>@@")
(format-time-string "%Y-%m-%d" (org-publish-find-date entry project))
entry
(org-publish-find-title entry project))))
(defun org-blog-sitemap-function (title list)
"Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'."
(concat "#+TITLE: " title "\n"
"#+AUTHOR: " author-name "\n"
"#+EMAIL: " author-email "\n"
"\n#+begin_archive\n"
(mapconcat (lambda (li)
(format "@@html:<li>@@ %s @@html:</li>@@" (car li)))
(seq-filter #'car (cdr list))
"\n")
"\n#+end_archive\n"))
(defun org-blog-publish-to-html (plist filename pub-dir)
"Same as `org-html-publish-to-html' but modifies html before finishing."
(let ((file-path (org-html-publish-to-html plist filename pub-dir)))
(with-current-buffer (find-file-noselect file-path)
(goto-char (point-min))
(search-forward "<body>")
(insert (mapconcat 'identity
'("<input type=\"radio\" id=\"light\" name=\"theme\"/>"
"<input type=\"radio\" id=\"dark\" name=\"theme\"/>"
"<input type=\"radio\" id=\"raw\" name=\"theme\"/>"
"<input type=\"radio\" id=\"darkraw\" name=\"theme\"/>"
"<div id=\"labels\">"
"<div class=\"content\">"
"Change theme: "
"<label for=\"light\">Light</label>"
"(<label for=\"raw\">raw</label>)"
" / "
"<label for=\"dark\">Dark</label>"
"(<label for=\"darkraw\">raw</label>)"
"</div>"
"</div>"
"<div class=\"main\">")
"\n"))
(goto-char (point-max))
(search-backward "</body>")
(insert "\n</div>\n")
(save-buffer)
(kill-buffer))
file-path))
(setq org-publish-project-alist
`(("orgfiles"
:base-directory ,base-dir
:exclude ".*drafts/.*"
:base-extension "org"
:publishing-directory ,publish-dir
:recursive t
:publishing-function org-blog-publish-to-html
:with-toc nil
:with-title nil
:with-date t
:section-numbers nil
:html-doctype "html5"
:html-html5-fancy t
:html-head-include-default-style nil
:html-head-include-scripts nil
:htmlized-source t
:html-head-extra ,org-blog-head
:html-preamble org-blog-preamble
:html-postamble org-blog-postamble
:auto-sitemap t
:sitemap-filename "archive.org"
:sitemap-title "Blog Posts"
:sitemap-style list
:sitemap-sort-files anti-chronologically
:sitemap-format-entry org-blog-sitemap-format-entry
:sitemap-function org-blog-sitemap-function)
("assets"
:base-directory ,assets-dir
:base-extension ".*"
:exclude ".*\.org$"
:publishing-directory ,publish-assets-dir
:publishing-function org-publish-attachment
:recursive t)
("rss"
:base-directory ,rss-dir
:base-extension "org"
:html-link-home ,domainname
:html-link-use-abs-url t
:rss-extension "xml"
:publishing-directory ,publish-rss-dir
:publishing-function (org-rss-publish-to-rss)
:exclude ".*"
:include ("archive.org")
:section-numbers nil
:table-of-contents nil)
("blog" :components ("orgfiles" "assets" "rss"))))
;; add target=_blank and rel="noopener noreferrer" to all links by default
(defun my-org-export-add-target-blank-to-http-links (text backend info)
"Add target=\"_blank\" to external links."
(when (and
(org-export-derived-backend-p backend 'html)
(string-match "href=\"http[^\"]+" text)
(not (string-match "target=\"" text))
(not (string-match (concat "href=\"" domainname "[^\"]*") text)))
(string-match "<a " text)
(replace-match "<a target=\"_blank\" rel=\"noopener noreferrer\" " nil nil text)))
(add-to-list 'org-export-filter-link-functions
'my-org-export-add-target-blank-to-http-links)
#+end_src

View file

@ -2,7 +2,7 @@
#+SUBTITLE: Meta Post (not really related to Donal Knuth)
#+AUTHOR: Yann Esposito
#+EMAIL: yann@esposito.host
#+DATE: [2019-07-28]
#+DATE: [2019-08-17 Sat]
#+KEYWORDS: programming, blog, org-mode, web, css
#+OPTIONS: auto-id:t
@ -43,6 +43,9 @@ priority:
Some of the constraints are straightforward to get, some other not.
You can also check that not using more resources one of the theme of my
website looks quite more classical and modern that my preferred ones.
** Respect Privacy
:PROPERTIES:
:CUSTOM_ID: respect-privacy
@ -104,7 +107,9 @@ You should at least be informed a new article has been published.
This one is a bit tricky.
It would mean, that visiting my website should not consume much resources.
Mainly, this would prevent using heavy medias as much as possible.
So, no video, no animated gif, no image if possible or very sparse one.
So, no video, no animated gif, no image if possible or very compressed small one.
So I have a script that convert all images to maximize site to `800x800`
and use at max 16 colors. On my current example image the size goes from 3.1MB to 88KB.
* How
:PROPERTIES:
@ -237,13 +242,34 @@ You want:
- obfuscate your email to prevent spam
- link to your email with a link to the current page integrated in the body/subject
- replace your external link to open in a new tab securely (=noopener / noreferrer=).
- compress images during publishing
Also, a single detail make using org-publish a bit awkward compared to
classical other classical static website generators; it is designed to be
set in you full emacs configuration.
But I wanted to be able to clone my git repository and be able to generate
my website locally even if I clone it on different directories.
So I created a package just for that: [[file:project-el/index.org][Autoload eLISP file in projects]].
*** Tree of files
:PROPERTIES:
:CUSTOM_ID: tree-of-files
:END:
There is a first pass that use =projectile= emacs package to detect the
current root file of the project and provide a list of absolute paths.
Then you set the associative list =org-publish-project-alist= with many
straightforward details.
The source directory, the destination directory, but also, file to exclude,
a function used to transform org files to HTML, etc...
:PROPERTIES:
:CUSTOM_ID: tree-of-files
:END:
#+begin_src elisp
(setq domainname "https://her.esy.fun")
(setq domainname "https://john.doe")
(setq base-dir (concat (projectile-project-root) "src"))
(setq publish-dir (concat (projectile-project-root) "_site"))
(setq assets-dir (concat base-dir "/"))
@ -253,8 +279,8 @@ You want:
(setq publish-rss-dir publish-dir)
(setq publish-rss-img (concat domainname "/rss.png"))
(setq css-path "/css/minimalist.css")
(setq author-name "Yann Esposito")
(setq author-email "yann@esposito.host")
(setq author-name "John Doe")
(setq author-email "john@doe.com")
(require 'org)
(require 'ox-publish)
@ -301,7 +327,7 @@ You want:
:base-extension ".*"
:exclude ".*\.org$"
:publishing-directory ,publish-assets-dir
:publishing-function org-publish-attachment
:publishing-function org-blog-publish-attachment
:recursive t)
("rss"
@ -325,6 +351,9 @@ You want:
:CUSTOM_ID: html-headers
:END:
I set the header to provide a link to the RSS file, the CSS, the favicon
and viewport directive for mobile browsers.
#+begin_src elisp
(defvar org-blog-head
(concat
@ -338,6 +367,11 @@ You want:
:PROPERTIES:
:CUSTOM_ID: preamble---postamble
:END:
So I put a menu in both the preamble and postamble.
The postamble contains a lot of details about the article, author, email,
date, etc...
#+begin_src elisp
(defun menu (lst)
"Blog menu"
@ -378,10 +412,6 @@ You want:
"Post-amble for whole blog."
(concat
"<div class=\"content\">"
;; TODO install a comment system
;; (let ((url (format "%s%s" domainname (replace-regexp-in-string base-dir "" (plist-get info :input-file)))))
;; (format "<a href=\"https://comments.esy.fun/slug/%s\">comment</a>"
;; (url-hexify-string url)))
"<footer>"
(when-let ((author (get-from-info info :author)))
(if-let ((email (plist-get info :email)))
@ -415,28 +445,13 @@ You want:
(menu '("<a href=\"#preamble\">↑ Top ↑</a>"))
"</div>"))
#+end_src
*** RSS
:PROPERTIES:
:CUSTOM_ID: rss-db16
:END:
#+begin_src elisp
("rss"
:base-directory ,rss-dir
:base-extension "org"
:html-link-home ,domainname
:html-link-use-abs-url t
:rss-extension "xml"
:publishing-directory ,publish-rss-dir
:publishing-function (org-rss-publish-to-rss)
:exclude ".*"
:include ("archive.org")
:section-numbers nil
:table-of-contents nil)
#+end_src
*** Obfuscate email
:PROPERTIES:
:CUSTOM_ID: obfuscate-email
:END:
A simple function to obfuscate HTML by using hexadecimal and octal notation.
#+begin_src elisp
(defun rand-obfs (c)
(let ((r (% (random) 20)))
@ -452,6 +467,16 @@ You want:
:PROPERTIES:
:CUSTOM_ID: specific-email-subject
:END:
You can set a subject to an email when you click on it by writing a link
that looks like:
#+begin_example
mailto:john@doe.com?subject=the-subject
#+end_example
Of course most of it is obfuscated.
#+begin_src elisp
(let* ((obfs-email (obfuscate-html email))
(obfs-author (obfuscate-html author))
@ -469,6 +494,8 @@ You want:
:CUSTOM_ID: nice-external-links
:END:
Also, why not fix our external link (see [[https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/][this article]] as reference):
#+begin_src elisp
;; add target=_blank and rel="noopener noreferrer" to all links by default
(defun my-org-export-add-target-blank-to-http-links (text backend info)
@ -484,14 +511,55 @@ You want:
(add-to-list 'org-export-filter-link-functions
'my-org-export-add-target-blank-to-http-links)
#+end_src
*** Image compression
:PROPERTIES:
:CUSTOM_ID: image-compression
:END:
to compress images I use [[https://imagemagick.org][imagemagick]] like this:
#+begin_src bash
convert src/a.png -resize 800x800\> +dither -colors 16 -depth 4 dest/a.png
#+end_src
For example:
#+CAPTION: Compressed Image
#+NAME: fig:compressed-image
#+ATTR_HTML: A Compressed Image
[[../img/a.png]]
I compress automatically images during publishing with:
#+begin_src elisp
(defun org-blog-publish-attachment (plist filename pub-dir)
"Publish a file with no transformation of any kind.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Take care of minimizing the pictures using imagemagick.
Return output file name."
(unless (file-directory-p pub-dir)
(make-directory pub-dir t))
(or (equal (expand-file-name (file-name-directory filename))
(file-name-as-directory (expand-file-name pub-dir)))
(let ((dst-file (expand-file-name (file-name-nondirectory filename) pub-dir)))
(if (string-match-p ".*\\.\\(png\\|jpg\\|gif\\)$" filename)
(shell-command
(format "convert %s -resize 800x800\\> +dither -colors 16 -depth 4 %s"
filename
dst-file))
(copy-file filename dst-file t)))))
#+end_src
*** Full code
:PROPERTIES:
:CUSTOM_ID: full-code
:END:
Here is the full code:
#+begin_src elisp
(setq domainname "https://her.esy.fun")
(setq domainname "https://john.doe")
(setq base-dir (concat (projectile-project-root) "src"))
(setq publish-dir (concat (projectile-project-root) "_site"))
(setq assets-dir (concat base-dir "/"))
@ -501,8 +569,8 @@ You want:
(setq publish-rss-dir publish-dir)
(setq publish-rss-img (concat domainname "/rss.png"))
(setq css-path "/css/minimalist.css")
(setq author-name "Yann Esposito")
(setq author-email "yann@esposito.host")
(setq author-name "John Doe")
(setq author-email "john@doe.com")
(require 'org)
(require 'ox-publish)
@ -660,6 +728,25 @@ You want:
(kill-buffer))
file-path))
(defun org-blog-publish-attachment (plist filename pub-dir)
"Publish a file with no transformation of any kind.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Take care of minimizing the pictures using imagemagick.
Return output file name."
(unless (file-directory-p pub-dir)
(make-directory pub-dir t))
(or (equal (expand-file-name (file-name-directory filename))
(file-name-as-directory (expand-file-name pub-dir)))
(let ((dst-file (expand-file-name (file-name-nondirectory filename) pub-dir)))
(if (string-match-p ".*\\.\\(png\\|jpg\\|gif\\)$" filename)
(shell-command
(format "convert %s -resize 800x800\\> +dither -colors 16 -depth 4 %s"
filename
dst-file))
(copy-file filename dst-file t)))))
(setq org-publish-project-alist
`(("orgfiles"
:base-directory ,base-dir
@ -696,7 +783,7 @@ You want:
:base-extension ".*"
:exclude ".*\.org$"
:publishing-directory ,publish-assets-dir
:publishing-function org-publish-attachment
:publishing-function org-blog-publish-attachment
:recursive t)
("rss"

View file

@ -2,7 +2,7 @@
#+SUBTITLE: fast, secure, easy autoload
#+AUTHOR: Yann Esposito
#+EMAIL: yann@esposito.host
#+DATE: [2019-07-28]
#+DATE: [2019-08-17 Sat]
#+KEYWORDS: programming, blog, org-mode
#+OPTIONS: auto-id:t