" it)
it)))))
(defmacro org-deftag (name args docstring &rest body)
"Re-render an Org section in any way you like, by tagging the section with NAME.
That is to say, we essentially treat tags as functions that act on Org headings:
We redefine Org sections for the same purposes as Org special blocks.
Anyhow:
ARGS are the sequence of items seperated by underscores after the NAME of the new tag.
BODY is a form that may anaphorically mention:
- O-BACKEND: The backend we are exporting to, such as `latex' or `html'.
- O-HEADING: The string denoting the title of the tagged section heading.
DOCSTRING is mandatory; everything should be documented for future maintainability.
The result of this anaphoric macro is a symbolic function name `org-deftag/NAME',
which is added to `org-export-before-parsing-hook'.
----------------------------------------------------------------------
Below is the motivating reason for inventing this macro. It is used:
** Interesting, but low-priority, content :details_red:
Blah blah blah blah blah blah blah blah blah blah blah.
Blah blah blah blah blah blah blah blah blah blah blah.
Here is the actual implementation:
(org-deftag details (color)
\"HTML export a heading as if it were a block; COLOR is an optional
argument indicating the background colour of the resulting block.\"
(insert \"\n#+html:\"
(format \"\" color)
\"\" (s-replace-regexp \"^\** \" \"\" heading) \"\")
(org-next-visible-heading 1)
(insert \"#+html: \"))
"
(let ((func-name (intern (format "org-deftag/%s" name))))
`(progn
(cl-defun ,func-name (o-backend)
,docstring
(outline-show-all)
(org-map-entries
(lambda ()
(kill-line)
(let ((o-heading (car kill-ring)))
(if (not (s-contains? (format ":%s" (quote ,name)) o-heading 'ignoring-case))
(insert o-heading)
(-let [,args (cdr (s-split "_" (car (s-match (format "%s[^:]*" (quote ,name)) o-heading))))]
(setq o-heading (s-replace-regexp (format ":%s[^:]*:" (quote ,name)) "" o-heading))
,@body)
;; Otherwise we impede on the auto-inserted “* footer :ignore:”
(insert "\n"))))))
(add-hook 'org-export-before-parsing-hook (quote ,func-name))
(quote ,func-name))))
(org-deftag details (anchor color)
"HTML export a heading as if it were a block; ANCHOR & COLOR are optional
arguments indicating the anchor for this block as well as the background colour of the resulting block.
For example, in my blog, I would use :details_rememberthis_#F47174: to mark a section as
friendly-soft-red to denote it as an “advanced” content that could be ignored
on a first reading of my article.
Incidentally, `orange' and `#f2b195' are also nice ‘warning’ colours."
(insert "\n#+html:"
(format "
"))
(use-package org-static-blog)
(use-package lf) ;; So we can use `lf-string' for multi-line strings supporting interpolation:
;; (lf-string "100/2 is ${ (/ 100 2) }; neato!") ;; ⇒ "100/2 is 50; neato!"
(defvar blog/title "Life & Computing Science"
"Title of the blog.")
(defvar blog/url "https://alhassy.com"
"URL of the blog.")
(defvar blog/publish-directory "~/blog/"
"Directory containing published HTML files.")
(defvar blog/posts-directory "~/blog/posts"
"Directory containing source Org files.
When publishing, posts are rendered as HTML and included in the index and RSS feed.
See `blog/make-index-page' and `blog/publish-directory'.")
(defun blog/new-article ()
"Make a new article for my blog; prompting for the necessary ingredients.
If the filename entered already exists, we simply write to it.
The user notices this and picks a new name.
This sets up a new article based on existing tags and posts.
+ Use C-SPC to select multiple tag items
Moreover it also enables `org-preview-html-mode' so that on every alteration,
followed by a save, C-x C-s, will result in a live preview of the blog article,
nearly instantaneously."
(interactive)
(let (file desc)
(thread-last blog/posts-directory
f-entries
(mapcar #'f-filename)
(completing-read "Filename (Above are existing): ")
(concat blog/posts-directory)
(setq file))
;; For some reason, ‘find-file’ in the thread above
;; wont let the completing-read display the possible completions.
(find-file file)
(insert "#+title: " (read-string "Title: ")
"\n#+author: " user-full-name
"\n#+email: " user-mail-address
;; "\n#+date: " (format-time-string "<%Y-%m-%d %H:%M>")
"\n#+filetags: " (s-join " " (helm-comp-read "Tags: "
blog/tags
:marked-candidates t))
"\n#+fileimage: emacs-birthday-present.png"
;; "\n#+fileimage: " (completing-read
;; "Image: "
;; (mapcar #'f-filename (f-entries "~/blog/images/")))
;; "\n#+include: ../MathJaxPreamble.org" ;; TODO. Is this someting I actually want here? If so, then consider tangling it from AlBasmala! (and add the whitespace-MathJax setup from above!)
"\n#+description: "
(setq desc (read-string "Article Purpose: "))
"\n\n* Abstract :ignore: \n" desc
"\n\n* ???")
(save-buffer)
(blog/preview)))
(defun blog/create-posts-json-file ()
"Create cache info about posts."
(interactive)
(require 'json)
(cl-loop for file in (f-files "~/blog/posts")
when (s-ends-with? ".org" file)
collect (blog/info file) into posts
finally
;; Sorted in descending time; i.e., the latest article should be first
(setq posts (sort posts (lambda (newer older) (time-less-p (date-to-time (@date older)) (date-to-time (@date newer))))))
(f-write-text (json-encode posts) 'utf-8 (f-expand "~/blog/posts.json"))
(find-file "~/blog/posts.json")
(json-pretty-print-buffer)
(write-file "~/blog/posts.json")))
(defvar blog/posts (with-temp-buffer (insert-file-contents "~/blog/posts.json") (json-parse-buffer))
"Load cached info about posts")
(defvar blog/tags (sort (seq-uniq (-flatten (seq-map (lambda (it) (s-split " " (map-elt it "tags"))) blog/posts))) #'string<)
"Tags for my blog articles.")
;; Convenient accessor methods: Given a JSON hashmap, get the specified key values.
;; Later, we redefine these, for example `@image' will actually produces the HTML for the image.
;; Example usage: (@title (seq-elt posts 0)) ⇒ "Java CheatSheet"
;; Extract the ‘#+title:‘ from POST-FILENAME.
(defun @title (json) (map-elt json "title"))
;; TODO: Consider using: (format-time-string "%d %b %Y" ⋯) to have the same format across all articles.
(defun @date (json)
"Extract the “#+date:” from JSON."
(map-elt json "date"))
(defun @file (json) (map-elt json "file"))
(defun @description (json) (map-elt json "description"))
(defun @abstract (json) (map-elt json "abstract"))
;; Returns absolute URL to the published POST-FILENAME.
;;
;; This function concatenates publish URL and generated custom filepath to the
;; published HTML version of the post.
;;
(defun @url (json) (map-elt json "url"))
(defun @history (json)
"Get an HTML badge that points to the Github history of a given file name, in my blog."
(concat
""))
(defun @tags (json)
"Get an HTML listing of tags, as shields.io bages, associated with the given file.
Example use: (@tags (seq-elt blog/posts 0))
"
(concat
;; Straightforward implementation.
;; "
"
;; Badges implementation
(concat
(format " %s "
(org-link/octoicon "tag" nil 'html))
(s-join " "
(--map (org-link/badge
(format "|%s|grey|%stag-%s.html"
(s-replace "-" "_" it)
"https://alhassy.com/" it)
nil 'html)
(s-split " " (map-elt json "tags")))))))
(cl-defun @image (json &optional explicit-image-path-prefix)
"Assemble the value of ‘#+fileimage: image width height border?’ as an HTML form.
By default, the image should be located in the top-level `images/' directory.
If the image is located elsewhere, or is a URL, is dictated by the presence of a `/'
in the image path.
Example use: (@image (seq-elt blog/posts 0))
Here are 4 example uses:
#+fileimage: emacs-birthday-present.png
#+fileimage: ../images/emacs-birthday-present.png
#+fileimage: https://upload.wikimedia.org/wikipedia/en/6/64/Dora_and_Boots.jpg 350 300
#+fileimage: https://unsplash.com/photos/Vc2dD4l57og
+ Notice that the second indicates explicit width and height.
+ (To make the first approach work with local previews,
we need the variable EXPLICIT-IMAGE-PATH-PREFIX which is used for local previews in `my/blog/style-setup'. This requires a slash at the end.)
+ The unsplash approach is specific: It shows the *main* image in the provided URL, and links to the provided URL.
"
(-let [(image width height no-border?) (s-split " " (map-elt json "image"))]
(setq width (or width 350))
(setq height (or height 350))
(setq no-border? (if no-border? "" "style=\"border: 2px solid black;\""))
(cond
((s-contains? "/" image) t) ;; It's a URL, or explicit path, do nothing to it.
(explicit-image-path-prefix (setq image (format "%s%s" explicit-image-path-prefix image)))
((not (s-contains? "/" image)) (setq image (format "images/%s" image))))
(-let [unsplash (cl-second (s-match ".*unsplash.com/photos/\\(.*\\)" image))]
(setq href (if unsplash (concat "https://unsplash.com/photos/" unsplash) image))
(setq title (format "Image credit “%s”" (if unsplash (concat "https://unsplash.com/photos/" unsplash) image)))
(setq src (if unsplash (format "https://source.unsplash.com/%s/%sx%s" unsplash width height) image))
(s-collapse-whitespace
(format "
"
href title src no-border? width height)))))
(defun blog/info (post-filename)
"Extract the `#+BLOG_KEYWORD: VALUE` pairs from POST-FILENAME.
Example use: (blog/info \"~/blog/posts/HeytingAlgebra.org\")
"
(let ((case-fold-search t))
(with-temp-buffer
(insert-file-contents post-filename)
(-snoc
(cons
(cons "file" (f-base post-filename))
(cl-loop for (prop.name prop.regex prop.default) on
`("title" "^\\#\\+title:[ ]*\\(.+\\)$" ,post-filename
"date" "^\\#\\+date:[ ]*<\\([^]>]+\\)>$" ,(time-since 0)
"image" "^\\#\\+fileimage: \\(.*\\)" "emacs-birthday-present.png 350 350"
"description" "^\\#\\+description:[ ]*\\(.+\\)$" "I learned something neat, and wanted to share!"
"tags" "^\\#\\+filetags:[ ]*\\(.+\\)$" "" ;; String; Space separated sequence of tags
)
by 'cdddr
;; See: https://stackoverflow.com/questions/19774603/convert-alist-to-from-regular-list-in-elisp
do (goto-char (point-min))
collect (cons prop.name
(if (search-forward-regexp prop.regex nil t)
(match-string 1)
prop.default))))
(cons "url" (concat "https://alhassy.com/" (f-base post-filename)))
(cons "history" (format "https://github.com/alhassy/alhassy.github.io/commits/master/posts/%s.org"
(f-base post-filename)))
(cons "abstract" (progn
(goto-char (point-min))
(when (re-search-forward "^\* Abstract" nil t)
(beginning-of-line)
(-let [start (point)]
(org-narrow-to-subtree)
(org-fold-show-entry)
(re-search-forward "^ *:END:" nil t) ;; Ignore :PROPERTIES: drawer, if any.
(forward-line)
(buffer-substring-no-properties (point) (point-max))))))))))
(org-defblock abstract (main) nil
"Render a block in a slightly narrowed blueish box, titled \"Abstract\".
Supported backends: HTML. "
(format (concat
"
"
"
Abstract
"
"%s
")
contents))
(cl-defun blog/make-index-page ()
"Assemble the blog index page.
The index page contains blurbs of all of my articles.
Precondition: `blog/posts' refers to all posts, in reverse chronological order.
You can view the generated ~/blog/index.html by invoking:
(blog/make-index-page)
"
(interactive)
(blog/make-tags-page :export-file-name "~/blog/index.html"))
(defun blog/make-all-tag-pages ()
"Make tag pages for all of my tags"
(interactive)
(loop for total = (length blog/tags)
for tag in blog/tags
for n from 0
for progress = (* (/ (* n 1.0) total) 100)
do
(let ((inhibit-message t)) (blog/make-tags-page :tag tag))
(message "Progress ... %d%%" progress)
;; Slightly faster to generate all pages, /then/ to git add them all.
;; TODO: I'm doing a “git commit” here, where else? Maybe merge them all together? Likewise with “git add”s.
finally (shell-command "cd ~/blog; git add \"tag-*.html\"; git commit -m \"Generated tags file\"")))
;; NOTE: Slightly faster if I get rid of the “Progress…” notifications.
(cl-defun blog/make-tags-page
(&key
(tag nil)
(title (if tag (format "Posts tagged “%s”" tag) ""))
(greeting (format "Here are some of my latest thoughts %s... badge:Made_with|Lisp|success|https://alhassy.github.io/ElispCheatSheet/CheatSheet.pdf|Gnu-Emacs such as doc:thread-first and doc:loop (•̀ᴗ•́)و tweet:https://alhassy.com @@html:
@@"
(if tag (concat "on " tag) "")))
(export-file-name
(concat-to-dir blog/publish-directory
(if tag (concat "tag-" (downcase tag) ".html")
"index.html"))))
"Assemble a page of only articles tagged TAG blog index page.
The page contains blurbs of all of my articles tagged TAG.
Precondition: `blog/posts' refers to all posts, in reverse chronological order.
Example uses:
1. (blog/make-tags-page :export-file-name \"~/blog/index.html\" :title \"Hello world\" :tag \"arabic\")
2. (blog/make-tags-page :tag \"arabic\")
"
(interactive)
(view-echo-area-messages)
(blog/preview/disable)
(with-temp-buffer
(insert
(s-join
"\n"
(list
;; TODO: Actually look at this concat result and notice that osbe is adding
;; way too much content to the header!
;; (progn (org-special-block-extras-mode -1) "")
(setq org-html-head-extra "")
;; Org-mode header
(concat "#+EXPORT_FILE_NAME: " export-file-name)
"#+options: toc:nil title:nil html-postamble:nil"
"#+begin_export html"
;; MA: Not ideal, the sizes I've set in the actual article are best.
;; ""
org-static-blog-page-preamble
org-static-blog-page-header
"#+end_export"
(concat "#+html:
" title "
")
;; TODO: Delete the following comment when things work and are done.
;; Extra styling of abstracts.
;; Works; but not needed.
;; "\n#+HTML_HEAD_EXTRA: "
;; The greeting message that informs viewers what this page is about.
"#+html: " greeting "#+html: "
;; TODO: Add this loop body to the info of each post, for future use via AngularJS view-by-tags.
;; Blurbs of posts
(s-join "\n" (--map
(if (and tag (not (seq-contains-p (s-split " " (map-elt it "tags")) tag)))
""
(concat
(progn (message "Processing %s..." it) "") ;; Progress indicator
;; TODO: Make this concat of ⟨0⟩-⟨4⟩ into a method `@preview'
;; ⟨0⟩ Title and link to article
(format "#+HTML:
" (@url it) (@title it))
;; TODO: Look at all uses of @title and maybe move this @@html@@ into it.
;; NOTE: This is still a Phase Split since the JSON has the raw data, and the @ABC methods produce @@html⋯@@ code ^_^
;; ⟨1⟩ Tags and reading time
(format "\n#+begin_export html\n
%s\n
\n#+end_export" (@tags it)) ;; TODO: Look at all uses of @tags and maybe move this @@html@@ into it.
;; ⟨2⟩ Article image
(format "\n@@html:%s@@\n" (@image it)) ;; TODO: Look at all uses of @image and maybe move this @@html@@ into it.
;; ⟨3⟩ Preview
(@abstract it)
;; ⟨4⟩ “Read more” link
;; TODO: Make a @read-more method.
(format (concat "\n@@html:
@@") (@url it))))
(seq--into-list blog/posts)))
;; “Show older posts”
;; This is the bottom-most matter in the index.html page
"#+begin_export html"
"
Thanks for reading everything! 😁 Bye! 👋
"
(blog/license) ;; TODO: Add a “proudly created with Emacs’ Org-mode” tagline?
"\n#+end_export"
)))
(org-mode)
(org-html-export-to-html)))
(org-deflink blog
"Provide the styles for “www.alhassy.com”'s “header” and “footer”.
The use of “blog:footer” aims to provide a clickable list of tags, produce an HTMLized version of the Org source,
and provides a Disqus comments sections. For details, consult the `blog/footer' function.
Finally, I want to avoid any `@@backend:...@@' from appearing in the browser frame's title.
We accomplish this with the help of some handy-dandy JavaScript: Just use “blog:sanitise-title”.
"
(pcase o-label
("header" (concat
org-static-blog-page-preamble
org-static-blog-page-header
""
""
""
;; The use of the “post-title” class is so that the org-static-blog-assemble-rss method can work as intended.
(thread-last (org-static-blog-get-title (buffer-file-name))
(s-replace-regexp "@@html:" "")
(s-replace-regexp "@@" "")
(format "
%s
"))))
("footer" (blog/footer (buffer-file-name)))
("sanitise-title" "")
(_ "")))
(defun blog/style-setup (_backend)
"Insert blog header (fancy title), tags, blog image (before “* Abstract”), and footer (links to tags).
There are default options: TOC is at 2 levels, no classic Org HTML postamble nor drawers are shown.
Notice that if you explicitly provide options to change the toc, date, or show drawers, etc;
then your options will be honoured. (Since they will technically come /after/ the default options,
which I place below at the top of the page.)
"
(goto-char (point-min))
(let ((post (blog/info (buffer-file-name))))
(insert "#+options: toc:2 html-postamble:nil d:nil"
"\n#+date: " (format-time-string "%Y-%m-%d" (current-time))
(if (buffer-narrowed-p) "\n#+options: broken-links:t" "")
"\n blog:header blog:sanitise-title \n"
"\n* Tags, then Image :ignore:"
"\n#+html: "
"
"
(@tags post)
"
"
"\n#+html: "
(@image post
;; Need this conditional since AlBasmala lives in ~/blog whereas usually articles live in ~/blog/posts.
;; TODO: Consider just making AlBasmala live in ~/blog/posts, I don't think there's any real reason for breaking consistency.
(if (equal (f-base (@file post)) "AlBasmala") "./images/" "../images/"))
"\n")
;; Wrap contents of “* Abstract” section in the “abstract” Org-special-block
;; (In case we are narrowed, we only act when we can find the Abstract.)
;; TODO: Replace this with (@abstract (blog/info (buffer-file-name))), or: (@abstract post)
(when (re-search-forward "^\* Abstract" nil t)
(beginning-of-line)
(-let [start (point)]
(org-narrow-to-subtree)
(org-show-entry)
(re-search-forward "^ * :END:" nil t) ;; Ignore :PROPERTIES: drawer, if any.
(forward-line)
(insert "\n#+begin_abstract\n")
(call-interactively #'org-forward-heading-same-level)
;; In case there is no next section, just go to end of file.
(when (equal start (point)) (goto-char (point-max)))
(insert "\n#+end_abstract\n")
(widen)))
(goto-char (point-max))
;; The Org file's title is already shown via blog:header, above, so we disable it in the preview.
(insert (format "\n* footer :ignore: \n blog:footer \n #+options: title:nil \n"))))
(cl-defun blog/preview ()
"Enable preview-on-save, and add blog/style-setup from Org's export hook."
(interactive)
;; Let's ensure we have no xwidget buffer lying around, otherwise Emacs might hang.
(-let [kill-buffer-query-functions nil]
(mapcar #'kill-buffer (--filter (equal 'xwidget-webkit-mode (buffer-local-value 'major-mode it)) (buffer-list))))
;; Inserting org-link/blog /seamlessly/ via the export process
(add-hook 'org-export-before-processing-hook #'blog/style-setup)
;; Preview with every save
(setq org-preview-html-viewer 'xwidget)
(org-preview-html-mode))
(cl-defun blog/preview/disable ()
"Disable preview-on-save, and remove blog/style-setup from Org's export hook."
(interactive)
(remove-hook 'org-export-before-processing-hook #'blog/style-setup)
(org-preview-html-mode -1))
(defun blog/footer (post-file-name)
"Returns the HTML rendering the htmlised source, version history, and comment box at the end of a post.
This function is called for every post and the returned string is appended to the post body, as a postamble."
(let ((post (blog/info (buffer-file-name))))
(concat
""
"
"
(blog/htmlize-file post-file-name)
" "
(@history post)
;;
;; Consider only add this to posts tagged “arabic”?
(blog/css/arabic-font-setup)
;;
" "
""
""
;;
" Generated by Emacs and Org-mode (•̀ᴗ•́)و "
(blog/license)
;; (blog/comments) ;; TODO. Not working as intended; low priority.
"
"
;; The next line is required to make the org-static-blog-assemble-rss method work.
"
"
(blog/read-remaining-js))))
(defun blog/htmlize-file (file-name)
"Generate an htmlized version of a given source file; return an HTML badge linking to the colourised file.
We do not take the extra time to produce a colourised file when we are previewing an article."
(unless org-preview-html-mode
(let ((org-hide-block-startup nil))
(with-temp-buffer
(find-file file-name)
;; (insert "\n#+HTML_HEAD: \n")
(org-mode)
(outline-show-all)
(switch-to-buffer (htmlize-buffer))
(write-file (concat "~/blog/" (f-base file-name) ".org.html"))
(kill-buffer))))
(concat
""))
(defun blog/license ()
"Get HTML for Creative Commons Attribution-ShareAlike 3.0 Unported License."
(s-collapse-whitespace (s-replace "\n" ""
"
")))
(defun blog/comments ()
"Embed Disqus Comments for my blog"
(s-collapse-whitespace (s-replace "\n" ""
"
comments powered by Disqus")))
(defun blog/read-remaining-js ()
"Get the HTML required to make use of ReadRemaining.js"
;; [Maybe Not True] ReadReamining.js does not work well with xWidget browser within Emacs
(if (equal org-preview-html-viewer 'xwidget)
""
;; ReadRemaining.js ∷ How much time is left to finish reading this article?
;;
;; jQuery already loaded by org-special-block-extras.
;; ""
"
"))
(setq org-static-blog-page-header
(concat
;; NOPE: org-html-head-extra ;; Altered by ‘org-special-block-extras’
(concat
"
"
""
""
""
""
"")
""
"
"
"
"
))
(setq org-static-blog-page-preamble
"
")
;; Table captions should be below the tables
(setq org-html-table-caption-above nil
org-export-latex-table-caption-above nil)
(advice-add 'org-html--translate :before-until 'blog/display-toc-as-Ξ)
;; (advice-remove 'org-html--translate 'display-toc-as-Ξ)
(defun blog/display-toc-as-Ξ (phrase info)
(when (equal phrase "Table of Contents")
(s-collapse-whitespace
"
Ξ
")))
;; I'd like to have tocs and numbered headings
(setq org-export-with-toc t)
(setq org-export-with-section-numbers t)
(defun blog/ensure-useful-section-anchors (&rest _)
"Org sections without an ID are given one based on its title.
All non-alphanumeric characters are cleverly replaced with ‘-’.
If multiple trees end-up with the same id property, issue a
message and undo any property insertion thus far.
E.g., ↯ We'll go on a ∀∃⇅ adventure
↦ We'll-go-on-a-adventure
"
(interactive)
(let ((ids))
(org-map-entries
(lambda ()
(org-with-point-at (point)
(let ((id (org-entry-get nil "CUSTOM_ID")))
(unless id
(thread-last (nth 4 (org-heading-components))
(s-replace-regexp "[^[:alnum:]']" "-")
(s-replace-regexp "-+" "-")
(s-chop-prefix "-")
(s-chop-suffix "-")
(setq id))
(if (not (member id ids))
(push id ids)
(message-box "Oh no, a repeated id!\n\n\t%s" id)
(undo)
(setq quit-flag t))
(org-entry-put nil "CUSTOM_ID" id))))))))
;; Whenever html & md export happens, ensure we have headline ids.
(advice-add 'org-html-export-to-html :before 'blog/ensure-useful-section-anchors)
(advice-add 'org-md-export-to-markdown :before 'blog/ensure-useful-section-anchors)
;; Src: https://writepermission.com/org-blogging-clickable-headlines.html
(setq org-html-format-headline-function
(lambda (todo todo-type priority text tags info)
"Format a headline with a link to itself."
(let* ((headline (get-text-property 0 :parent text))
(id (or (org-element-property :CUSTOM_ID headline)
(ignore-errors (org-export-get-reference headline info))
(org-element-property :ID headline)))
(link (if id
(format "%s" id text)
text)))
(org-html-format-headline-default-function todo todo-type priority link tags info))))
(defun blog/css/arabic-font-setup ()
"Setup the CSS/HTML required to make my Arabic writing look nice.
For a one-off use in an article, just place an “#+html:” in front of the result
of this function."
"
")
(cl-defun blog/git (cmd &rest args)
"Execute git command CMD, which may have %s placeholders whose values are positional in ARGS."
(shell-command (apply #'format (concat "cd ~/blog; git " cmd) args)))
(cl-defun blog/publish-current-article ()
"Place HTML files in the right place, update index, rss, tags; git push!"
(interactive)
(blog/git "add %s" (buffer-file-name))
;; Placed article html into the published blog directory
(blog/preview)
(save-buffer)
(-let [article (concat (f-base (buffer-file-name)) ".html")]
(shell-command (concat "mv " article " ~/blog/"))
(blog/git "add %s %s" (buffer-file-name) article)
;; Make AlBasmala live with the other posts to avoid this conditional.
(when (equal (f-base (buffer-file-name)) "AlBasmala")
(blog/git "add AlBasmala.el")))
;; Need to disable my export-preprocessing hooks.
(blog/preview/disable) (view-echo-area-messages)
(message "⇒ HTMLizing article...") (blog/htmlize-file (buffer-file-name))
(message "⇒ Assembling tags...") (blog/make-all-tag-pages) ;; TODO: I only need to update the tags pages relevant to the current article!
(message "⇒ Assembling RSS feed...") (org-static-blog-assemble-rss)
(message "⇒ Assembling landing page...") (blog/make-index-page)
(blog/git "add %s.org.html tag* rss.xml index.html" (f-base (buffer-file-name)))
;; TODO: If we're updating an existing article, prompt for a message.
(blog/git "commit -m \"%s\"; git push"
(if current-prefix-arg
(read-string "Commit message: ")
(format "Publish: Article %s.org" (f-base (buffer-file-name)))))
(message "⇒ It may take up 20secs to 1minute for changes to be live at alhassy.com; congratulations!"))