diff --git a/.gitignore b/.gitignore index 4743da6..eb1cbb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ _cache _site -src/archive.org +src/posts/rss.org +src/posts/posts.org +src/micro/rss.org diff --git a/project.el b/project.el index c83af5e..1510327 100644 --- a/project.el +++ b/project.el @@ -11,6 +11,7 @@ (defvar micro-publish-dir (concat publish-dir "/micro")) (defvar rss-dir base-dir) (defvar rss-title "Subscribe to articles") +(defvar micro-rss-title "Subscribe to micro blogs") (defvar posts-descr "Articles") (defvar micro-descr "Short micro blog entries à la twitter/mastodon") (defvar publish-rss-dir publish-dir) @@ -32,7 +33,8 @@ (concat "" "" - "" + "" + "" "")) (defun menu (lst) @@ -42,7 +44,7 @@ (mapconcat 'identity (append '("Home" - "Posts" + "Posts" "Slides" "About") lst) @@ -135,19 +137,34 @@ (menu '("↑ Top ↑")) "")) -(defun org-blog-sitemap-format-entry (entry _style project) +(defun org-blog-sitemap-format-entry (sub entry _style project) "Return string for each ENTRY in PROJECT." (cond ((not (directory-name-p entry)) (let* ((file (org-publish--expand-file-name entry project)) (title (org-publish-find-title entry project)) (date (format-time-string "%Y-%m-%d" (org-publish-find-date entry project))) - (link (concat (file-name-sans-extension entry) ".html"))) + (link (concat domainname "/" sub "/" (file-name-sans-extension entry) ".html"))) (with-temp-buffer (insert (format "* [[file:%s][%s]]\n" file title)) (org-set-property "RSS_PERMALINK" link) (org-set-property "PUBDATE" date) (org-id-get-create) (insert-file-contents file) + + (goto-char 0) + (end-of-line) + (forward-char) + (while (re-search-forward "^#\\+.*$" nil t) + (progn + (replace-match "") + (kill-line))) + + (goto-char 0) + (end-of-line) + (forward-char) + (while (re-search-forward "^\\*" nil t) + (replace-match "**")) + (buffer-string)))) ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))))) @@ -159,7 +176,7 @@ "#+EMAIL: " author-email "\n" "#+DESCRIPTION: " descr "\n" (mapconcat (lambda (li) - (format "* %s" (car li))) + (format "%s" (car li))) (seq-filter #'car (cdr list)) "\n"))) @@ -221,10 +238,19 @@ Return output file name." (defalias 'org-blog-micro-sitemap-fn (apply-partially 'org-blog-sitemap-fn-descr micro-descr)) +(defalias 'org-blog-sitemap-format-entry-posts + (apply-partially 'org-blog-sitemap-format-entry "posts")) + +(defalias 'org-blog-sitemap-format-entry-micro + (apply-partially 'org-blog-sitemap-format-entry "micro")) + +(defun donothing (_x _y _z) + nil) + (setq org-publish-project-alist `(("orgfiles" :base-directory ,base-dir - :exclude ".*(drafts|posts|micro)/.*" + :exclude ".*\\(drafts\\|posts\||micro\\)/.*" :base-extension "org" :publishing-directory ,publish-dir :recursive t @@ -243,6 +269,18 @@ Return output file name." :html-postamble org-blog-postamble :auto-sitemap nil) + ("posts-index" + :base-directory ,posts-dir + :base-extension "org" + :publishing-directory "/dev/null" + :recursive t + :publishing-function donothing + :auto-sitemap t + :sitemap-filename "posts.org" + :sitemap-title "Articles" + :sitemap-style list + :sitemap-sort-files anti-chronologically) + ("posts" :base-directory ,posts-dir :base-extension "org" @@ -262,11 +300,11 @@ Return output file name." :html-preamble org-blog-preamble :html-postamble org-blog-postamble :auto-sitemap t - :sitemap-filename "posts.org" - :sitemap-title "Blog Posts" + :sitemap-filename "rss.org" + :sitemap-title "Used For RSS" :sitemap-style list :sitemap-sort-files anti-chronologically - :sitemap-format-entry org-blog-sitemap-format-entry + :sitemap-format-entry org-blog-sitemap-format-entry-posts :sitemap-function org-blog-posts-sitemap-fn) ("micro" @@ -288,11 +326,11 @@ Return output file name." :html-preamble org-blog-preamble :html-postamble org-blog-postamble :auto-sitemap t - :sitemap-filename "micro.org" + :sitemap-filename "rss.org" :sitemap-title "Micro Blog Posts" :sitemap-style list :sitemap-sort-files anti-chronologically - :sitemap-format-entry org-blog-sitemap-format-entry + :sitemap-format-entry org-blog-sitemap-format-entry-micro :sitemap-function org-blog-micro-sitemap-fn) ("assets" @@ -312,11 +350,12 @@ Return output file name." :publishing-directory ,publish-rss-dir :publishing-function (org-rss-publish-to-rss) :exclude ".*" - :include ("posts/posts.org" "micro/micro.org") + :include ("posts/rss.org" + "micro/rss.org") :section-numbers nil :table-of-contents nil) - ("blog" :components ("orgfiles" "posts" "assets" "rss")))) + ("blog" :components ("orgfiles" "posts" "posts-index" "micro" "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) diff --git a/project.el.sig b/project.el.sig index 1cc77a6..7890141 100644 Binary files a/project.el.sig and b/project.el.sig differ diff --git a/src/micro/ping.org b/src/micro/ping.org index 7ddca83..1f51e0a 100644 --- a/src/micro/ping.org +++ b/src/micro/ping.org @@ -9,3 +9,10 @@ #+STARTUP: showeverything Ping! + +* This is a test +:PROPERTIES: +:CUSTOM_ID: this-is-a-test +:END: + +pong! diff --git a/src/posts/posts.org b/src/posts/posts.org index 1e71644..c69eb01 100644 --- a/src/posts/posts.org +++ b/src/posts/posts.org @@ -1,1172 +1,6 @@ -#+TITLE: Blog Posts -#+AUTHOR: Yann Esposito -#+EMAIL: yann@esposito.host -#+DESCRIPTION: Articles -* * [[file:/Users/yaesposi/y/her.esy.fun/src/posts/project-el/index.org][Autoload Script by project]] -:PROPERTIES: -:RSS_PERMALINK: project-el/index.html -:PUBDATE: 2019-08-17 -:ID: FB591D7B-8153-44F5-8D40-3F8C83858529 -:CUSTOM_ID: ----file--users-yaesposi-y-her-esy-fun-src-posts-project-el-index-org--autoload-script-by-project-- -:END: -#+TITLE: Autoload Script by project -#+SUBTITLE: fast, secure, easy autoload -#+AUTHOR: Yann Esposito -#+EMAIL: yann@esposito.host -#+DATE: [2019-08-17 Sat] -#+KEYWORDS: programming, blog, org-mode -#+OPTIONS: auto-id:t - -#+begin_quote -/tl;dr/: A script that use projectile and GPG to securely load -an emacs lisp script when opening a new project. - -Check the [[#solution]] section to get the code. -#+end_quote - -* Problem - :PROPERTIES: - :CUSTOM_ID: problem - :ID: BD82DF52-CF0F-4E40-8E3A-AA04479E3071 - :PUBDATE: <2019-08-31 Sat 14:19> - :END: - -When providing a repository containing only org files of my blog. -I also wanted to provide everything necessary for users to be able to publish my -website. -Emacs, org-publish mostly assume you should put all those details in a -centralised place in your =~/.emacs.d/.init.el= file. - -The main principle is quite simple. - -1. When opening a new file in a project, check for the presence of a - =project.el= and =project.el.sig= file at the root directory of the - project. -2. Check the file was signed with by a trusted fingerprint. -3. Load the file. - -Other solutions I found on the internet asked you each time you enter in a -project if you trust the file or not. -This was both quite annoying and insecure as it is kind of easy to type 'y' -instead of 'n' and to load 3rd party script. - -Note that checking who signed a file with an external signature is not as -straightforward as it should be: - -#+begin_src elisp -(defun auto-load-project/get-sign-key (file) - "Return the fingerprint of they key that signed FILE. - -To sign a file you should used - -`gpg --local-user my@email --output project.el.sig --detach-sign project.el`" - (string-trim-right - (shell-command-to-string - (concat - (format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file) - "|grep VALIDSIG" - "|awk '{print $3}'")))) -#+end_src - -- The `--status-fd` should provide more script friendly output. - GPG provide localized output by default which are therefore hard to use in - script (for grep for example). - -We use =projectile= to detect the project-root and when we are in a new project. -Unfortunately the =projectile-after-switch-project-hooks= doesn't work as I -expected. -So I use the hooks =find-file-hook= and =dired-mode-hook= to try to load the -file. -In order not to load the code each time, I need to keep a local state of project -already loaded. - -So now, each time I modify the =project.el= I sign it with the following -command line: - -#+begin_src bash -gpg --local-user my@email --output project.el.sig --detach-sign project.el -#+end_src - -* Solution - :PROPERTIES: - :CUSTOM_ID: solution - :ID: DC21FA06-1531-4944-855A-B61EBA1681B1 - :PUBDATE: <2019-08-31 Sat 14:19> - :END: - -The project is hosted here: https://gitlab.esy.fun/yogsototh/auto-load-project-el - -You can setup the emacs package in spacemacs with: - -#+begin_src elisp - ;; ... - dotspacemacs-additional-packages - '((auto-load-project :location - (recipe - :fetcher git - :url "https://gitlab.esy.fun/yogsototh/auto-load-project-el" - :files ("auto-load-project.el")))) - ;; ... - (defun dotspacemacs/user-config () - ;; ... - (require 'auto-load-project) - (setq auto-load-project/trusted-gpg-key-fingerprints - '("0000000000000000000000000000000000000000" ;; figerprint of trusted key 1 - "1111111111111111111111111111111111111111" - "2222222222222222222222222222222222222222" - ))) - ;; ... -#+end_src - -The full current code should be easy to follow if you have basic notions -of eLISP: - -#+begin_src elisp -(require 'projectile) - -(defvar auto-load-project/trusted-gpg-key-fingerprints - '() - "The list of GPG fingerprint you trust when decrypting a gpg file. -You can retrieve the fingerprints of your own private keys -with: `gpg --list-secret-keys' (take care of removing the -spaces when copy/pasting here)") - -(defun auto-load-project/get-sign-key (file) - "Return the fingerprint of they key that signed FILE. - -To sign a file you should used - -`gpg --local-user my@email --output project.el.sig --detach-sign project.el`" - (string-trim-right - (shell-command-to-string - (concat - (format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file) - "|grep VALIDSIG" - "|awk '{print $3}'")))) - -(defun auto-load-project/trusted-gpg-origin-p (file) - "Return non-nil if the FILE is encrypted with a trusted key." - (member (auto-load-project/get-sign-key file) - auto-load-project/trusted-gpg-key-fingerprints)) - -(defconst auto-load-project/project-file "project.el" - "Project configuration file name.") - -(defvar auto-load-project/loaded-projects (list) - "Projects that have been loaded by `auto-load-project/load'.") - -(defun auto-load-project/load () - "Loads the `auto-load-project/project-file' for a project. -This is run once the project is loaded signifying project setup." - (interactive) - (when (projectile-project-p) - (lexical-let* ((current-project-root (projectile-project-root)) - (project-init-file (expand-file-name auto-load-project/project-file current-project-root)) - (project-sign-file (concat project-init-file ".sig"))) - (when (and (not (member current-project-root auto-load-project/loaded-projects)) - (file-exists-p project-init-file) - (file-exists-p project-sign-file) - (auto-load-project/trusted-gpg-origin-p project-init-file)) - (message "Loading project init file for %s" (projectile-project-name)) - (condition-case ex - (progn (load project-init-file) - (add-to-list 'auto-load-project/loaded-projects current-project-root) - (message "%s loaded successfully" project-init-file)) - ('error - (message - "There was an error loading %s: %s" - project-init-file - (error-message-string ex)))))))) - -(add-hook 'find-file-hook #'auto-load-project/load t) -(add-hook 'dired-mode-hook #'auto-load-project/load t) - -(provide 'auto-load-project) -#+end_src - -* * [[file:/Users/yaesposi/y/her.esy.fun/src/posts/troll-2/index.org][Troll 2]] -:PROPERTIES: -:RSS_PERMALINK: troll-2/index.html -:PUBDATE: 2019-08-17 -:ID: 4CF9E289-E019-4403-8F61-9F7FBC3AB052 -:CUSTOM_ID: ----file--users-yaesposi-y-her-esy-fun-src-posts-troll-2-index-org--troll-2-- -:END: -#+Title: Troll 2 -#+Subtitle: How a terrible movie can be entertaining -#+Author: Yann Esposito -#+Email: yann@esposito.host -#+Date: [2019-08-17 Sat] -#+KEYWORDS: movie -#+DESCRIPTION: -#+LANGUAGE: en -#+LANG: en -#+OPTIONS: H:5 auto-id:t -#+STARTUP: showeverything - -#+begin_notes -I watched what may be the worse movie of all time and I still enjoyed -greatly the show. -#+end_notes - -I wanted to watch an horror teen movie I saw when I was a kid; Troll. -During my searches, I discovered there is another movie named "Troll 2". -I thought that it is certainly the sequel. - -I took a look to the imdb note, and it was very bad (2.9 / 10). -But, hey, I decided to watch it. -Here is an overview of my watching experience. - -* The watching -:PROPERTIES: -:CUSTOM_ID: the-watching -:ID: F1444E4E-88F3-44EA-8E3B-5A313A558964 -:PUBDATE: <2019-08-31 Sat 14:19> -:END: - -The synopsis of Troll 2 is a family living in a city that exchange its -house to go to the village of Nilbog "to live like farmers"... -The son of the family see his dead grandfather. -The grandpa warn his grandson against Goblins. -Those creatures make you eat green things that transform you and then they -eat you. -Of course, nobody listen to the boy that does not want to go to Nilbog, -which is infested by goblins that can take Human appearance. - -Troll 2 is bad on most criterion you use to measure the quality of a movie. - -During the first minutes of the movie, you get to see the costume of the -goblins. -Those costume looks very bad and cheap. -So much you can only find them not terrorizing but funny and ridiculous. - -#+CAPTION: One goblin during the introduction scene of Troll 2 -#+NAME: fig:troll-2-intro -#+ATTR_HTML: A goblin -[[./Troll-2-intro.jpg]] - -Soon after that, you realize the acting of all actors is extremely bad. -In fact, it is so bad, you might not believe me how bad it is. -To give you an idea, the only equal bad acting I ever witnessed was while -looking at amateurs first Youtube movies trying to follow a scenario. -Apparently most actors were amateurs, it was their first and last movie. - -#+CAPTION: One particularly terrible acting scene -#+NAME: fig:bad-acting -#+ATTR_HTML: A bad acting demonstration -[[file:bad-acting.png]] - -The dialog are, really something... -For example the expression "clusters of hemorrhoid" is used in a non ironic -dialog. - -The scenario is terrible. -For example, most of the thing occurring suffer from terrible plot holes or -terrible mistakes that make everything hard to believe even if you accept -the premises of a world were Goblins would exist. -For example, the grandfather ghost can stop time for 30 seconds for no -reason at all. - -The realization is a series of basic mistakes. -Actors are not in the same places after all plan cut for example. -Some filmed scene feel so wrong. - -I forgot to give a word about the music. -It is like the director choose the worst music to go along each scene. - -The first ending is really, quite surprising. -They win against the monsters with, what I believe was a failed attempt at -humor. -It misses the point so bad, that the irony still make it funny. - -#+CAPTION: Our hero save the day by urinating on the table. His family is frozen for 30s said grandpa, they were for 70s. -#+NAME: fig:prevent-eating -#+ATTR_HTML: Eliott prevents his family to eat the food by urinating on the table -[[./prevent-eating-scene.jpg]] - -Of course, the very last scene is a classical so terrible cliché. -To let you close the experience in awe. - -But there is a bonus, the cherry on the cake. -During all the movie, it is *never* question of a Troll at all. -*There is not Troll in Troll 2*. - -Still, it was quite entertaining. - -# LocalWords: Nilbog cliché - -* After the movie -:PROPERTIES: -:CUSTOM_ID: after-the-movie -:ID: 44290CAD-7467-4D01-AB15-9A2D5A01B6C5 -:PUBDATE: <2019-08-31 Sat 14:19> -:END: - -What is really interesting though is that I really enjoyed the watch. -It was so bad, that I couldn't enter in the movie. -I analyzed the movie, and saw all its failures and noticed everything wrong -about it. -In the end, the experience was still quite enjoyable. -For example, there are some attempts at humor, but almost all of them fail -terribly. -I didn't laugh about the joke, but I did nonetheless by thinking about how -bad that joke was. - -Once going to [[https://www.imdb.com/title/tt0105643/][imdb]], I discovered I wasn't alone in loving that terrible -movie. -Now, I don't know if I should give that movie a 1 or 2 stars or 8 to 9 -stars because it was so entertaining. - -Since then, I learned a few anecdotes about that movie. - -It was realized in America by Claudio Fragasso who didn't speak fluent -English. -He and his wife were apparently irritated by many of her friends turning -vegetarian. -He brought the film crew over with him from Italy. -None of them spoke English either. - -#+begin_quote -“The cast had few experienced actors, and was primarily assembled from -residents of nearby towns who responded to an open casting call. - -George Hardy was a dentist with no acting experience who showed up for fun, -hoping to be cast as an extra, only to be given one of the film’s largest -speaking roles. - -Don Packard, who played the store owner, was actually a resident at a -nearby mental hospital, and was cast for—and filmed—his role while on a day -trip; after recovering and being released from the hospital, he recalled -that he had no idea what was happening around him, and that his disturbed -“performance” in the film was not acting”. -#+end_quote - -Also that movie is so bad, there is a documentary about that movie named: -[[https://www.imdb.com/title/tt1144539]["Best Worst Movie"]]. - - - -* * [[file:/Users/yaesposi/y/her.esy.fun/src/posts/new-blog.org][New Blog]] -:PROPERTIES: -:RSS_PERMALINK: new-blog.html -:PUBDATE: 2019-08-17 -:ID: 27E9B039-E66E-4419-96BD-6235EA38954A -:CUSTOM_ID: ----file--users-yaesposi-y-her-esy-fun-src-posts-new-blog-org--new-blog-- -:END: -#+TITLE: New Blog -#+SUBTITLE: Meta Post (not really related to Donald Knuth) -#+AUTHOR: Yann Esposito -#+EMAIL: yann@esposito.host -#+DATE: [2019-08-17 Sat] -#+KEYWORDS: programming, blog, org-mode, web, css -#+OPTIONS: auto-id:t - -#+begin_notes -tl;dr: The first blog post of a blog should certainly be about the blog -itself to provide a feeling of self-reference. -#+end_notes - -* Peaceful and Respectful Website -:PROPERTIES: -:CUSTOM_ID: peaceful-and-respectful-website -:ID: 9C715987-0E8C-49CB-8165-94C730989B91 -:PUBDATE: <2019-08-31 Sat 14:19> -:END: - -There is a trend about website being quite less accessible, using more and -more resources, adding trackers, popups, videos, animations, big js -frameworks, etc... - -I wanted a more peaceful and respectful website. - -That website was created with the following constraints in mind by order of -priority: - -1. *Respect Privacy*; no tracker of any sort (no ads, no google analytics, no - referrer for all external links, etc...) -2. *nearly no javascript*; no js at all except for a single exception, - pages containing Math formula are displayed using mathjax. That means - that event the CSS theme switcher does not use javascript. -3. *Accessible*; should be easy to read on a text browser so people with - disabilities could easily consume it -4. *nerdy*; should feel mostly like markdown text in a terminal and source - code should be syntax highlighted. -5. *theme switchable*; support your preferred light/dark theme by default - but you can change it if you want. -6. *rss*; you should be able to get informed when I add a new blog post. -7. *frugal*; try to minimize the resources needed to visit my website; no - javascript, no web-font, not too much CSS magic, not much images or really - compressed one. - -Some of the constraints are straightforward to get, some 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 - :ID: 085ADDF5-D910-4D0E-BA26-F07060D89E37 - :END: -The one should be easy, simply not put any 3rd party inclusion in my website. -So, no external CSS in my headers, no link to any image I do not host myself. -No 3rd party javascript. - -** Javascript Free -:PROPERTIES: - :CUSTOM_ID: javascript-free - :ID: 882ED09A-B7E9-4AD9-AB15-9C817EE000B3 - :END: -I do not really see why a content oriented website should need to execute javascript. - -** Accessible -:PROPERTIES: -:CUSTOM_ID: disability-friendly -:ID: 854BF110-84CF-45D3-99A4-4F7473BFD6DC -:END: -A good way to check that a website is friendly to disabled people is by -looking at it with a text browser. -If you open most website today you see that at the top of the page is -crippled with a numerous number of links/metas info used for javascript -tricks, login/logout buttons, etc... -The website should only contain, a pretty minimal menu to navigate, and the -content. - -** Nerdy -:PROPERTIES: -:CUSTOM_ID: nerdy -:ID: 08E2BE72-C7FD-4E86-92BF-1DB97B9A6F44 -:END: -The feel of the website should be nerdy, it should look like reading a -terminal or emacs. -It should almost feel the same as if you were using a text-browser. -For sensible people, I added a "modern" theme that should better suit -modern eye, still the first design should always be the terminal looking -one. - -** Theme switchable -:PROPERTIES: -:CUSTOM_ID: theme-switchable -:ID: B2061EBE-C79C-49B8-AC57-5FA5D6961F21 -:END: -Even if you are not used to disability friendly browser. -The website should try to guess your preferred way to consume my website. -Recently we dark/light themes were integrated as a new CSS feature. -This website should propose your apparently preferred theme. -But you could also change it manually. - -** RSS -:PROPERTIES: -:CUSTOM_ID: rss -:ID: F21DD644-92A1-4BEA-A3AC-4AB8BA03A60A -:END: -This is another layer that help you consume my website as you prefer. -You should at least be informed a new article has been published. - -** Frugal -:PROPERTIES: -:CUSTOM_ID: frugal -:ID: 29FFEBDC-B978-44E3-9C33-CB6302632037 -:END: -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 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: -:CUSTOM_ID: how -:ID: 21EA558C-C9BB-45F2-8F17-B544DBA6AC44 -:PUBDATE: <2019-08-31 Sat 14:19> -:END: -** CSS -:PROPERTIES: -:CUSTOM_ID: css -:ID: B117AFAE-BBA4-4DFB-B171-5B60B4E67412 -:END: -Regarding CSS, I always found that the default text display by navigator is -terrible. -So just to "fix" a minimal CSS to have something bearable it takes me about -120 lines of CSS. - -By fixing I mean things like using a fixed line width for text (there is an -optimal range to improve legibility). -Also having correct list indentation, line-height and font-size. -Table displaying correctly. - -Then I have about 90 lines of CSS to make my HTML look like text source of -a markdown. - -Then I set a few CSS rules to handle the ids and classes added by -org-export as instead of using the ubiquitous Markdown, I prefer greatly to -use org mode files. -I need 60 lines of CSS for them. - -In order to handle color themes (5 at the time of writing those lines) I -use almost 350 line to handle those. - -*** CSS Theme selection -:PROPERTIES: -:CUSTOM_ID: css-theme-selection -:ID: 4061F98F-1AA4-441A-A073-6E666A4B1AA1 -:END: - -One thing that wasn't straightforward while writing the CSS was to provide -an interactive theme selector without any javascript involved. -That theme switcher is really the limit I can concede to modern standards -because it is CSS only. - -The trick is to provide one top-level element per theme at the beginning of -the body of the HTML. -Then hide those elements (I chose inputs). -Finally provide a set of anchor links. - -#+begin_src html - ... - - - -
- Change theme: - Light - Dark -
-
- ALL YOUR CONTENT HERE -
- -#+end_src - -Then use the /sibling/ CSS selector =~=. -Then put all your content in a div of class =.main= for example. -Finally in the CSS you can write things like: - -#+begin_src css - /* hide all radio button that are not inside another div of body */ - body > input { - display: none; - } - :root { - --light-color: #fff; - --dark-color: #000; - } - input#light:target ~ .main { - background-color: var(--light-color); - color: var(--dark-color); - } - input#dark:target ~ .main { - background-color: var(--dark-color); - color: var(--light-color); - } -#+end_src - -I previously used checkbox inputs but using URL fragment feels better. - -** Blog Engine - org-mode with org-publish -:PROPERTIES: -:CUSTOM_ID: blog-engine---org-mode-with-org-publish -:ID: 674088E6-C71B-4C48-B785-43BE8C4A2ADB -:END: -So publishing a website is something that could go from. -Write your own HTML each time. -But this is quite tedious, so we generally all use a website generator. -The next thing with the minimal possible amount of work is using org-mode -with org-publish. -Because a website is mostly, export all of file in org-mode format (easier -to write and manipulate than raw HTML) to HTML. - -In fact, there are numerous details that make this task not this straightforward. -You want: - -1. from a tree of org-mode files, generate an equivalent file tree of HTML - files generated from the org-mode files. This is the main purpose of org-publish. -2. We also want to set specific headers, a CSS file, a favicon, link to RSS - file, mobile friendly directives. -3. Have common header/footer (preamble, postamble) if possible with a menu. -4. An archive page with a list of posts. -5. Generate an RSS file -6. Niceties: - - 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 -:ID: 9025C6FA-C1DE-4194-AE19-A1E735FDF6FD -: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... - -#+begin_src elisp - (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 "/")) - (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 publish-rss-img (concat domainname "/rss.png")) - (setq css-path "/css/minimalist.css") - (setq author-name "John Doe") - (setq author-email "john@doe.com") - - (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/")) - - (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-blog-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" - :rss-image-url ,publish-rss-img - :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")))) -#+end_src -*** HTML Headers -:PROPERTIES: -:CUSTOM_ID: html-headers -:ID: 97990AD3-218F-46E5-A5CD-7DCDDDF34653 -: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 - "" - "" - "" - "")) - -#+end_src -*** Preamble & Postamble -:PROPERTIES: -:CUSTOM_ID: preamble---postamble -:ID: 43B0C7DD-4CC8-4A6D-8E72-D8C3A242D927 -: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" - (concat - "" - (mapconcat 'identity - (append - '("Home" - "Posts" - "About") - lst) - " | ") - "")) - - (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 - "
" - (menu '("↓ bottom ↓")) - "

" - (format "%s" (car (plist-get info :title))) - "

" - (when-let ((date (plist-get info :date))) - (format "%s" - (format-time-string "%Y-%m-%d" - (org-timestamp-to-time - (car date))))) - (when-let ((subtitle (car (plist-get info :subtitle)))) - (format "

%s

" subtitle)) - "
")) - - (defun org-blog-postamble (info) - "Post-amble for whole blog." - (concat - "
" - "" - (menu '("↑ Top ↑")) - "
")) -#+end_src -*** Obfuscate email -:PROPERTIES: -:CUSTOM_ID: obfuscate-email -:ID: CF4CD280-7091-4ADB-969E-5D566D382F87 -:END: - -A simple function to obfuscate HTML by using hexadecimal and octal notation. - -#+begin_src elisp - (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))) -#+end_src -*** Specific email subject -:PROPERTIES: -:CUSTOM_ID: specific-email-subject -:ID: A137ADD3-F02F-4CEC-948C-75088D8276B4 -: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)) - (obfs-title (obfuscate-html (get-from-info info :title))) - (full-email (format "%s <%s>" obfs-author obfs-email))) - (format "
Author: %s
" - (obfuscate-html "mailto:") - full-email - (obfuscate-html "?subject=yblog: ") - obfs-title - full-email)) -#+end_src -*** Nice external links -:PROPERTIES: -:CUSTOM_ID: nice-external-links -:ID: 89B2526A-3315-460D-995E-A243C07F260D -: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) - "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 " +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 -:ID: 53F10956-E515-4DDB-B861-76189C60EDB8 -:END: - -Here is the full code: - -#+begin_src elisp - (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 "/")) - (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 publish-rss-img (concat domainname "/rss.png")) - (setq css-path "/css/minimalist.css") - (setq author-name "John Doe") - (setq author-email "john@doe.com") - - (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 - "" - "" - "" - "")) - - (defun menu (lst) - "Blog menu" - (concat - "" - (mapconcat 'identity - (append - '("Home" - "Posts" - "About") - lst) - " | ") - "")) - - (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 - "
" - (menu '("↓ bottom ↓")) - "

" - (format "%s" (car (plist-get info :title))) - "

" - (when-let ((date (plist-get info :date))) - (format "%s" - (format-time-string "%Y-%m-%d" - (org-timestamp-to-time - (car date))))) - (when-let ((subtitle (car (plist-get info :subtitle)))) - (format "

%s

" subtitle)) - "
")) - - (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 - "
" - ;; TODO install a comment system - ;; (let ((url (format "%s%s" domainname (replace-regexp-in-string base-dir "" (plist-get info :input-file))))) - ;; (format "comment" - ;; (url-hexify-string url))) - "" - (menu '("↑ Top ↑")) - "
")) - - (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:" - "@@ %s: @@html:@@" - " [[file:%s][%s]]" - " @@html:@@") - (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:
  • @@ %s @@html:
  • @@" (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 "") - (insert (mapconcat 'identity - '("" - "" - "" - "" - "
    " - "
    " - "Change theme: " - "" - "()" - " / " - "" - "()" - "
    " - "
    " - "
    ") - "\n")) - (goto-char (point-max)) - (search-backward "") - (insert "\n
    \n") - (save-buffer) - (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 - :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-blog-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" - :rss-image-url ,publish-rss-img - :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 "